django-orchestra/orchestra/apps/resources/models.py

186 lines
7.3 KiB
Python
Raw Normal View History

2014-07-10 15:19:06 +00:00
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
2014-07-08 15:19:15 +00:00
from django.contrib.contenttypes.models import ContentType
from django.core import validators
2014-07-11 22:08:16 +00:00
from django.db import models
2014-07-08 15:19:15 +00:00
from django.utils.translation import ugettext_lazy as _
from djcelery.models import PeriodicTask, CrontabSchedule
2014-07-09 16:17:43 +00:00
from orchestra.models.fields import MultiSelectField
2014-07-08 15:19:15 +00:00
2014-07-11 22:08:16 +00:00
from . import helpers
2014-07-09 16:17:43 +00:00
from .backends import ServiceMonitor
2014-07-08 15:19:15 +00:00
class Resource(models.Model):
2014-07-09 16:17:43 +00:00
"""
Defines a resource, a resource is basically an interpretation of data
gathered by a Monitor
"""
LAST = 'LAST'
MONTHLY_SUM = 'MONTHLY_SUM'
MONTHLY_AVG = 'MONTHLY_AVG'
2014-07-08 15:19:15 +00:00
PERIODS = (
2014-07-09 16:17:43 +00:00
(LAST, _("Last")),
(MONTHLY_SUM, _("Monthly Sum")),
(MONTHLY_AVG, _("Monthly Average")),
2014-07-08 15:19:15 +00:00
)
2014-07-16 15:20:16 +00:00
name = models.CharField(_("name"), max_length=32,
2014-07-08 15:19:15 +00:00
help_text=_('Required. 32 characters or fewer. Lowercase letters, '
'digits and hyphen only.'),
validators=[validators.RegexValidator(r'^[a-z0-9_\-]+$',
_('Enter a valid name.'), 'invalid')])
2014-07-16 15:20:16 +00:00
verbose_name = models.CharField(_("verbose name"), max_length=256)
2014-07-10 15:19:06 +00:00
content_type = models.ForeignKey(ContentType,
2014-07-16 15:20:16 +00:00
help_text=_("Model where this resource will be hooked."))
2014-07-11 14:48:46 +00:00
period = models.CharField(_("period"), max_length=16, choices=PERIODS,
default=LAST,
help_text=_("Operation used for aggregating this resource monitored"
"data."))
2014-07-10 15:19:06 +00:00
ondemand = models.BooleanField(_("on demand"), default=False,
help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand"))
2014-07-09 16:17:43 +00:00
default_allocation = models.PositiveIntegerField(_("default allocation"),
2014-07-11 14:48:46 +00:00
null=True, blank=True,
2014-07-10 15:19:06 +00:00
help_text=_("Default allocation value used when this is not an "
2014-07-11 14:48:46 +00:00
"on demand resource"))
2014-07-16 15:20:16 +00:00
unit = models.CharField(_("unit"), max_length=16,
help_text=_("The unit in which this resource is measured. "
"For example GB, KB or subscribers"))
scale = models.PositiveIntegerField(_("scale"),
help_text=_("Scale in which this resource monitoring resoults should "
"be prorcessed to match with unit."))
2014-07-10 15:19:06 +00:00
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
2014-07-11 14:48:46 +00:00
help_text=_("Disables monitors exeeded and recovery triggers"))
2014-07-10 10:03:22 +00:00
crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"),
2014-07-11 14:48:46 +00:00
null=True, blank=True,
help_text=_("Crontab for periodic execution. "
"Leave it empty to disable periodic monitoring"))
monitors = MultiSelectField(_("monitors"), max_length=256, blank=True,
2014-07-21 12:20:04 +00:00
choices=ServiceMonitor.get_plugin_choices(),
2014-07-11 14:48:46 +00:00
help_text=_("Monitor backends used for monitoring this resource."))
2014-07-16 15:20:16 +00:00
is_active = models.BooleanField(_("is active"), default=True)
class Meta:
unique_together = (
('name', 'content_type'),
('verbose_name', 'content_type')
)
2014-07-08 15:19:15 +00:00
def __unicode__(self):
2014-07-16 15:20:16 +00:00
return "{}-{}".format(str(self.content_type), self.name)
2014-07-08 15:19:15 +00:00
2014-07-10 10:03:22 +00:00
def save(self, *args, **kwargs):
super(Resource, self).save(*args, **kwargs)
# Create Celery periodic task
name = 'monitor.%s' % str(self)
try:
task = PeriodicTask.objects.get(name=name)
except PeriodicTask.DoesNotExist:
2014-07-10 15:19:06 +00:00
if self.is_active:
PeriodicTask.objects.create(name=name, task='resources.Monitor',
args=[self.pk], crontab=self.crontab)
else:
if not self.is_active:
task.delete()
elif task.crontab != self.crontab:
2014-07-10 10:03:22 +00:00
task.crontab = self.crontab
task.save()
2014-07-10 15:19:06 +00:00
2014-07-10 10:03:22 +00:00
def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs)
name = 'monitor.%s' % str(self)
PeriodicTask.objects.filter(name=name, task='resources.Monitor',
args=[self.pk]).delete()
2014-07-08 15:19:15 +00:00
@classmethod
def group_by_content_type(cls):
prev = None
group = []
2014-07-11 14:48:46 +00:00
resources = cls.objects.filter(is_active=True).order_by('content_type')
for resource in resources:
2014-07-08 15:19:15 +00:00
ct = resource.content_type
if prev != ct:
if group:
yield group
group = [resource]
else:
group.append(resource)
prev = ct
if group:
yield group
2014-07-10 10:03:22 +00:00
class ResourceData(models.Model):
""" Stores computed resource usage and allocation """
resource = models.ForeignKey(Resource, related_name='dataset')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
used = models.PositiveIntegerField(null=True)
last_update = models.DateTimeField(null=True)
2014-07-10 17:34:23 +00:00
allocated = models.PositiveIntegerField(null=True, blank=True)
2014-07-10 10:03:22 +00:00
2014-07-10 15:19:06 +00:00
content_object = GenericForeignKey()
2014-07-10 10:03:22 +00:00
class Meta:
unique_together = ('resource', 'content_type', 'object_id')
verbose_name_plural = _("resource data")
@classmethod
def get_or_create(cls, obj, resource):
2014-07-11 21:09:17 +00:00
ct = ContentType.objects.get_for_model(type(obj))
2014-07-10 10:03:22 +00:00
try:
2014-07-11 21:09:17 +00:00
return cls.objects.get(content_type=ct, object_id=obj.pk,
resource=resource)
except cls.DoesNotExist:
2014-07-10 10:03:22 +00:00
return cls.objects.create(content_object=obj, resource=resource,
2014-07-11 21:09:17 +00:00
allocated=resource.default_allocation)
2014-07-08 15:19:15 +00:00
2014-07-10 10:03:22 +00:00
def get_used(self):
2014-07-16 15:20:16 +00:00
return helpers.compute_resource_usage(self)
2014-07-08 15:19:15 +00:00
class MonitorData(models.Model):
2014-07-09 16:17:43 +00:00
""" Stores monitored data """
monitor = models.CharField(_("monitor"), max_length=256,
2014-07-21 12:20:04 +00:00
choices=ServiceMonitor.get_plugin_choices())
2014-07-08 15:19:15 +00:00
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
2014-07-16 15:20:16 +00:00
date = models.DateTimeField(_("date"), auto_now_add=True)
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
2014-07-08 15:19:15 +00:00
2014-07-10 15:19:06 +00:00
content_object = GenericForeignKey()
2014-07-08 15:19:15 +00:00
2014-07-09 16:17:43 +00:00
class Meta:
get_latest_by = 'date'
verbose_name_plural = _("monitor data")
2014-07-08 15:19:15 +00:00
def __unicode__(self):
return str(self.monitor)
2014-07-10 10:03:22 +00:00
def create_resource_relation():
2014-07-18 15:32:27 +00:00
class ResourceHandler(object):
""" account.resources.web """
def __getattr__(self, attr):
2014-07-21 15:43:36 +00:00
""" get or create ResourceData """
try:
return self.obj.resource_set.get(resource__name=attr)
except ResourceData.DoesNotExist:
model = self.obj._meta.model_name
resource = Resource.objects.get(content_type__model=model,
name=attr, is_active=True)
return ResourceData.objects.create(content_object=self.obj,
resource=resource)
2014-07-18 15:32:27 +00:00
def __get__(self, obj, cls):
self.obj = obj
return self
2014-07-10 15:19:06 +00:00
relation = GenericRelation('resources.ResourceData')
2014-07-10 10:03:22 +00:00
for resources in Resource.group_by_content_type():
model = resources[0].content_type.model_class()
2014-07-18 15:32:27 +00:00
model.add_to_class('resource_set', relation)
model.resources = ResourceHandler()