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

185 lines
7.3 KiB
Python

import datetime
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from djcelery.models import PeriodicTask, CrontabSchedule
from orchestra.models.fields import MultiSelectField
from orchestra.utils.apps import autodiscover
from .backends import ServiceMonitor
class Resource(models.Model):
"""
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'
PERIODS = (
(LAST, _("Last")),
(MONTHLY_SUM, _("Monthly Sum")),
(MONTHLY_AVG, _("Monthly Average")),
)
name = models.CharField(_("name"), max_length=32, unique=True,
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')])
verbose_name = models.CharField(_("verbose name"), max_length=256, unique=True)
content_type = models.ForeignKey(ContentType,
help_text=_("Model where this resource will be hooked"))
period = models.CharField(_("period"), max_length=16, choices=PERIODS, default=LAST,
help_text=_("Operation used for aggregating this resource monitored data."))
ondemand = models.BooleanField(_("on demand"), default=False,
help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand"))
default_allocation = models.PositiveIntegerField(_("default allocation"),
help_text=_("Default allocation value used when this is not an "
"on demand resource"),
null=True, blank=True)
is_active = models.BooleanField(_("is active"), default=True)
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
help_text=_("Disables monitor's resource exeeded and recovery triggers"))
crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"),
help_text=_("Crontab for periodic execution"))
monitors = MultiSelectField(_("monitors"), max_length=256,
choices=ServiceMonitor.get_choices())
def __unicode__(self):
return self.name
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:
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:
task.crontab = self.crontab
task.save()
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()
@classmethod
def group_by_content_type(cls):
prev = None
group = []
for resource in cls.objects.filter(is_active=True).order_by('content_type'):
ct = resource.content_type
if prev != ct:
if group:
yield group
group = [resource]
else:
group.append(resource)
prev = ct
if group:
yield group
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)
allocated = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey()
class Meta:
unique_together = ('resource', 'content_type', 'object_id')
verbose_name_plural = _("resource data")
@classmethod
def get_or_create(cls, obj, resource):
try:
return cls.objects.get(content_object=obj, resource=resource)
except cls.DoesNotExists:
return cls.objects.create(content_object=obj, resource=resource,
allocated=resource.defalt_allocation)
def get_used(self):
resource = self.resource
today = datetime.date.today()
result = 0
has_result = False
for monitor in resource.monitors:
dataset = MonitorData.objects.filter(monitor=monitor,
content_type=self.content_type, object_id=self.object_id)
if resource.period == resource.MONTHLY_AVG:
try:
last = dataset.latest()
except MonitorData.DoesNotExist:
continue
has_result = True
epoch = datetime(year=today.year, month=today.month, day=1)
total = (epoch-last.date).total_seconds()
dataset = dataset.filter(date__year=today.year,
date__month=today.month)
for data in dataset:
slot = (previous-data.date).total_seconds()
result += data.value * slot/total
elif resource.period == resource.MONTHLY_SUM:
data = dataset.filter(date__year=today.year,
date__month=today.month)
value = data.aggregate(models.Sum('value'))['value__sum']
if value:
has_result = True
result += value
elif resource.period == resource.LAST:
try:
result += dataset.latest().value
except MonitorData.DoesNotExist:
continue
has_result = True
else:
raise NotImplementedError("%s support not implemented" % self.period)
return result if has_result else None
class MonitorData(models.Model):
""" Stores monitored data """
monitor = models.CharField(_("monitor"), max_length=256,
choices=ServiceMonitor.get_choices())
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
date = models.DateTimeField(auto_now_add=True)
value = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Meta:
get_latest_by = 'date'
verbose_name_plural = _("monitor data")
def __unicode__(self):
return str(self.monitor)
def create_resource_relation():
relation = GenericRelation('resources.ResourceData')
for resources in Resource.group_by_content_type():
model = resources[0].content_type.model_class()
model.add_to_class('resources', relation)