# Copyright 2014 Budapest University of Technology and Economics (BME IK) # # This file is part of CIRCLE Cloud. # # CIRCLE is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. from __future__ import absolute_import, unicode_literals import logging import requests from django.conf import settings from django.core.exceptions import PermissionDenied from django.http import HttpResponse, Http404 from django.utils.translation import ugettext_lazy as _ from django.views.generic import View from braces.views import LoginRequiredMixin from vm.models import Instance, Node, InstanceTemplate logger = logging.getLogger(__name__) def register_graph(metric_cls, graph_name, graphview_cls): if not hasattr(graphview_cls, 'metrics'): graphview_cls.metrics = {} graphview_cls.metrics[graph_name] = metric_cls class GraphViewBase(LoginRequiredMixin, View): def create_class(self, cls): return type(str(cls.__name__ + 'Metric'), (cls, self.base), {}) def get(self, request, pk, metric, time, *args, **kwargs): graphite_url = settings.GRAPHITE_URL if graphite_url is None: raise Http404() try: metric = self.metrics[metric] except KeyError: raise Http404() try: instance = self.get_object(request, pk) except self.model.DoesNotExist: raise Http404() metric = self.create_class(metric)(instance) return HttpResponse(metric.get_graph(graphite_url, time), content_type="image/png") def get_object(self, request, pk): instance = self.model.objects.get(id=pk) if not instance.has_level(request.user, 'user'): raise PermissionDenied() return instance class Metric(object): cacti_style = True derivative = False scale_to_seconds = None metric_name = None title = None label = None def __init__(self, obj, metric_name=None): self.obj = obj self.metric_name = ( metric_name or self.metric_name or self.__class__.__name__.lower()) def get_metric_name(self): return self.metric_name def get_label(self): return self.label or self.get_metric_name() def get_title(self): return self.title or self.get_metric_name() def get_minmax(self): return (None, None) def get_target(self): target = '%s.%s' % (self.obj.metric_prefix, self.get_metric_name()) if self.derivative: target = 'nonNegativeDerivative(%s)' % target if self.scale_to_seconds: target = 'scaleToSeconds(%s, %d)' % (target, self.scale_to_seconds) target = 'alias(%s, "%s")' % (target, self.get_label()) if self.cacti_style: target = 'cactiStyle(%s)' % target return target def get_graph(self, graphite_url, time, width=500, height=200): params = {'target': self.get_target(), 'from': '-%s' % time, 'title': self.get_title().encode('UTF-8'), 'width': width, 'height': height} ymin, ymax = self.get_minmax() if ymin is not None: params['yMin'] = ymin if ymax is not None: params['yMax'] = ymax logger.debug('%s %s', graphite_url, params) response = requests.get('%s/render/' % graphite_url, params=params) return response.content class VmMetric(Metric): def get_title(self): title = super(VmMetric, self).get_title() return '%s (%s) - %s' % (self.obj.name, self.obj.vm_name, title) class NodeMetric(Metric): def get_title(self): title = super(NodeMetric, self).get_title() return '%s (%s) - %s' % (self.obj.name, self.obj.host.hostname, title) class VmGraphView(GraphViewBase): model = Instance base = VmMetric class NodeGraphView(GraphViewBase): model = Node base = NodeMetric def get_object(self, request, pk): if not self.request.user.has_perm('vm.view_statistics'): raise PermissionDenied() return self.model.objects.get(id=pk) class TemplateGraphView(GraphViewBase): model = InstanceTemplate base = Metric def get_object(self, request, pk): instance = super(TemplateGraphView, self).get_object(request, pk) if not instance.has_level(request.user, 'operator'): raise PermissionDenied() return instance class TemplateVms(object): metric_name = "instances.running" title = _("Instance count") label = _("instance count") def get_minmax(self): return (0, None) register_graph(TemplateVms, 'instances', TemplateGraphView) class NodeListGraphView(GraphViewBase): model = Node base = Metric def get_object(self, request, pk): if not self.request.user.has_perm('vm.view_statistics'): raise PermissionDenied() return Node.objects.filter(enabled=True) def get(self, request, metric, time, *args, **kwargs): if not self.request.user.has_perm('vm.view_statistics'): raise PermissionDenied() return super(NodeListGraphView, self).get(request, None, metric, time) class Ram(object): metric_name = "memory.usage" title = _("RAM usage (%)") label = _("RAM usage (%)") def get_minmax(self): return (0, 105) register_graph(Ram, 'memory', VmGraphView) register_graph(Ram, 'memory', NodeGraphView) class Cpu(object): metric_name = "cpu.percent" title = _("CPU usage (%)") label = _("CPU usage (%)") def get_minmax(self): if isinstance(self.obj, Node): return (0, 105) else: return (0, self.obj.num_cores * 100 + 5) register_graph(Cpu, 'cpu', VmGraphView) register_graph(Cpu, 'cpu', NodeGraphView) class VmNetwork(object): title = _("Network") def get_minmax(self): return (0, None) def get_target(self): metrics = [] for n in self.obj.interface_set.all(): params = (self.obj.metric_prefix, n.vlan.vid, n.vlan.name) metrics.append( 'alias(scaleToSeconds(nonNegativeDerivative(' '%s.network.bytes_recv-%s), 10), "out - %s (bits/s)")' % ( params)) metrics.append( 'alias(scaleToSeconds(nonNegativeDerivative(' '%s.network.bytes_sent-%s), 10), "in - %s (bits/s)")' % ( params)) return 'group(%s)' % ','.join(metrics) if metrics else None register_graph(VmNetwork, 'network', VmGraphView) class NodeNetwork(object): title = _("Network") def get_minmax(self): return (0, None) def get_target(self): return ( 'aliasSub(scaleToSeconds(nonNegativeDerivative(%s.network.b*),' '10), ".*\.bytes_(sent|recv)-([a-zA-Z0-9]+).*", "\\2 \\1")' % ( self.obj.metric_prefix)) register_graph(NodeNetwork, 'network', NodeGraphView) class NodeVms(object): metric_name = "vmcount" title = _("Instance count") label = _("instance count") def get_minmax(self): return (0, None) register_graph(NodeVms, 'vm', NodeGraphView) class NodeAllocated(object): title = _("Allocated memory (bytes)") def get_target(self): prefix = self.obj.metric_prefix if self.obj.online and self.obj.enabled: ram_size = self.obj.ram_size else: ram_size = 0 used = 'alias(%s.memory.used_bytes, "used")' % prefix allocated = 'alias(%s.memory.allocated, "allocated")' % prefix max = 'threshold(%d, "max")' % ram_size return 'cactiStyle(group(%s, %s, %s))' % (used, allocated, max) def get_minmax(self): return (0, None) register_graph(NodeAllocated, 'alloc', NodeGraphView) class NodeListAllocated(object): title = _("Allocated memory (bytes)") def get_target(self): nodes = self.obj used = ','.join('%s.memory.used_bytes' % n.metric_prefix for n in nodes) allocated = 'alias(sumSeries(%s), "allocated")' % ','.join( '%s.memory.allocated' % n.metric_prefix for n in nodes) max = 'threshold(%d, "max")' % sum( n.ram_size for n in nodes if n.online) return ('group(aliasSub(aliasByNode(stacked(group(%s)), 1), "$",' '" (used)"), %s, %s)' % (used, allocated, max)) def get_minmax(self): return (0, None) register_graph(NodeListAllocated, 'alloc', NodeListGraphView) class NodeListVms(object): title = _("Instance count") def get_target(self): vmcount = ','.join('%s.vmcount' % n.metric_prefix for n in self.obj) return 'group(aliasByNode(stacked(group(%s)), 1))' % vmcount def get_minmax(self): return (0, None) register_graph(NodeListVms, 'vm', NodeListGraphView)