Improvements on resource monitoring

This commit is contained in:
Marc 2014-07-10 15:19:06 +00:00
parent cc445559d0
commit 53a135a1d9
15 changed files with 137 additions and 68 deletions

View File

@ -48,5 +48,4 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* passlib; nano /usr/local/lib/python2.7/dist-packages/passlib/ext/django/utils.py SortedDict -> collections.OrderedDict * passlib; nano /usr/local/lib/python2.7/dist-packages/passlib/ext/django/utils.py SortedDict -> collections.OrderedDict
* pip install pyinotify * pip install pyinotify
* create custom field that returns backend python objects
* Backend.operations dynamically generated based on defined methods

View File

@ -71,6 +71,8 @@ class AccountAdmin(ExtendedModelAdmin):
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" Save user and account, they are interdependent """ """ Save user and account, they are interdependent """
if change:
return super(AccountAdmin, self).save_model(request, obj, form, change)
obj.user.save() obj.user.save()
obj.user_id = obj.user.pk obj.user_id = obj.user.pk
obj.save() obj.save()

View File

@ -22,7 +22,9 @@ class DomainViewSet(AccountApiMixin, viewsets.ModelViewSet):
@link() @link()
def view_zone(self, request, pk=None): def view_zone(self, request, pk=None):
domain = self.get_object() domain = self.get_object()
return Response({'zone': domain.render_zone()}) return Response({
'zone': domain.render_zone()
})
def metadata(self, request): def metadata(self, request):
ret = super(DomainViewSet, self).metadata(request) ret = super(DomainViewSet, self).metadata(request)

View File

@ -69,7 +69,7 @@ class ServiceBackend(object):
@classmethod @classmethod
def get_backend(cls, name): def get_backend(cls, name):
for backend in ServiceMonitor.get_backends(): for backend in ServiceBackend.get_backends():
if backend.get_name() == name: if backend.get_name() == name:
return backend return backend
raise KeyError('This backend is not registered') raise KeyError('This backend is not registered')

View File

@ -38,7 +38,7 @@ def message_user(request, logs):
errors = total-successes errors = total-successes
if errors: if errors:
msg = 'backends have' if errors > 1 else 'backend has' msg = 'backends have' if errors > 1 else 'backend has'
msg = _("%d out of %d {0} fail to executed".format(msg)) msg = _("%d out of %d {0} fail to execute".format(msg))
messages.warning(request, msg % (errors, total)) messages.warning(request, msg % (errors, total))
else: else:
msg = 'backends have' if successes > 1 else 'backend has' msg = 'backends have' if successes > 1 else 'backend has'

View File

@ -40,12 +40,12 @@ def BashSSH(backend, log, server, cmds):
channel = transport.open_session() channel = transport.open_session()
sftp = paramiko.SFTPClient.from_transport(transport) sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(path, path) sftp.put(path, "%s.remote" % path)
sftp.close() sftp.close()
os.remove(path) os.remove(path)
context = { context = {
'path': path, 'path': "%s.remote" % path,
'digest': digest 'digest': digest
} }
cmd = ( cmd = (

View File

@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.models.fields import NullableCharField
from orchestra.utils.apps import autodiscover from orchestra.utils.apps import autodiscover
from orchestra.utils.functional import cached from orchestra.utils.functional import cached
@ -14,9 +15,8 @@ from .backends import ServiceBackend
class Server(models.Model): class Server(models.Model):
""" Machine runing daemons (services) """ """ Machine runing daemons (services) """
name = models.CharField(_("name"), max_length=256, unique=True) name = models.CharField(_("name"), max_length=256, unique=True)
# TODO unique address with blank=True (nullablecharfield) address = NullableCharField(_("address"), max_length=256, blank=True,
address = models.CharField(_("address"), max_length=256, blank=True, null=True, unique=True, help_text=_("IP address or domain name"))
help_text=_("IP address or domain name"))
description = models.TextField(_("description"), blank=True) description = models.TextField(_("description"), blank=True)
os = models.CharField(_("operative system"), max_length=32, os = models.CharField(_("operative system"), max_length=32,
choices=settings.ORCHESTRATION_OS_CHOICES, choices=settings.ORCHESTRATION_OS_CHOICES,
@ -82,8 +82,7 @@ class BackendOperation(models.Model):
MONITOR = 'monitor' MONITOR = 'monitor'
log = models.ForeignKey('orchestration.BackendLog', related_name='operations') log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
# TODO backend and backend_class() (like content_type) backend = models.CharField(_("backend"), max_length=256)
backend_class = models.CharField(_("backend"), max_length=256)
action = models.CharField(_("action"), max_length=64) action = models.CharField(_("action"), max_length=64)
content_type = models.ForeignKey(ContentType) content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
@ -94,11 +93,11 @@ class BackendOperation(models.Model):
verbose_name_plural = _("Operations") verbose_name_plural = _("Operations")
def __unicode__(self): def __unicode__(self):
return '%s.%s(%s)' % (self.backend_class, self.action, self.instance) return '%s.%s(%s)' % (self.backend, self.action, self.instance)
def __hash__(self): def __hash__(self):
""" set() """ """ set() """
backend = getattr(self, 'backend', self.backend_class) backend = getattr(self, 'backend', self.backend)
return hash(backend) + hash(self.instance) + hash(self.action) return hash(backend) + hash(self.instance) + hash(self.action)
def __eq__(self, operation): def __eq__(self, operation):
@ -107,7 +106,7 @@ class BackendOperation(models.Model):
@classmethod @classmethod
def create(cls, backend, instance, action): def create(cls, backend, instance, action):
op = cls(backend_class=backend.get_name(), instance=instance, action=action) op = cls(backend=backend.get_name(), instance=instance, action=action)
op.backend = backend op.backend = backend
return op return op
@ -115,6 +114,9 @@ class BackendOperation(models.Model):
def execute(cls, operations): def execute(cls, operations):
return manager.execute(operations) return manager.execute(operations)
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
autodiscover('backends') autodiscover('backends')

View File

@ -1,8 +1,9 @@
from django.contrib import admin from django.contrib import admin, messages
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.filters import UsedContentTypeFilter from orchestra.admin.filters import UsedContentTypeFilter
from orchestra.admin.utils import insertattr, get_modeladmin from orchestra.admin.utils import insertattr, get_modeladmin
from orchestra.core import services from orchestra.core import services
@ -12,14 +13,34 @@ from .forms import ResourceForm
from .models import Resource, ResourceData, MonitorData from .models import Resource, ResourceData, MonitorData
class ResourceAdmin(admin.ModelAdmin): class ResourceAdmin(ExtendedModelAdmin):
# TODO warning message server/celery should be restarted when creating things
list_display = ( list_display = (
'name', 'verbose_name', 'content_type', 'period', 'ondemand', 'name', 'verbose_name', 'content_type', 'period', 'ondemand',
'default_allocation', 'disable_trigger' 'default_allocation', 'disable_trigger', 'crontab',
) )
list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger') list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger')
fieldsets = (
(None, {
'fields': ('name', 'content_type', 'period'),
}),
(_("Configuration"), {
'fields': ('verbose_name', 'default_allocation', 'ondemand',
'disable_trigger', 'is_active'),
}),
(_("Monitoring"), {
'fields': ('monitors', 'crontab'),
}),
)
change_readonly_fields = ('name', 'content_type', 'period')
def add_view(self, request, **kwargs):
""" Warning user if the node is not fully configured """
if request.method == 'GET':
messages.warning(request, _(
"Restarting orchestra and celery is required to fully apply changes. "
"Remember that allocated values will be applied when objects are saved"
))
return super(ResourceAdmin, self).add_view(request, **kwargs)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(ResourceAdmin, self).save_model(request, obj, form, change) super(ResourceAdmin, self).save_model(request, obj, form, change)

View File

@ -1,4 +1,9 @@
import datetime
from django.contrib.contenttypes.models import ContentType
from orchestra.apps.orchestration import ServiceBackend from orchestra.apps.orchestration import ServiceBackend
from orchestra.utils.functional import cached
class ServiceMonitor(ServiceBackend): class ServiceMonitor(ServiceBackend):
@ -14,14 +19,34 @@ class ServiceMonitor(ServiceBackend):
""" filter monitor classes """ """ filter monitor classes """
return [plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__] return [plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__]
def store(self, stdout): @cached
def get_last_date(self, obj):
from .models import MonitorData
try:
# TODO replace
#return MonitorData.objects.filter(content_object=obj).latest().date
ct = ContentType.objects.get(app_label=obj._meta.app_label, model=obj._meta.model_name)
return MonitorData.objects.filter(content_type=ct, object_id=obj.pk).latest().date
except MonitorData.DoesNotExist:
return self.get_current_date() - datetime.timedelta(days=1)
@cached
def get_current_date(self):
return datetime.datetime.now()
def store(self, log):
""" object_id value """ """ object_id value """
for line in stdout.readlines(): from .models import MonitorData
name = self.get_name()
app_label, model_name = self.model.split('.')
ct = ContentType.objects.get(app_label=app_label, model=model_name.lower())
for line in log.stdout.splitlines():
line = line.strip() line = line.strip()
object_id, value = line.split() object_id, value = line.split()
# TODO date MonitorData.objects.create(monitor=name, object_id=object_id,
MonitorHistory.store(self.model, object_id, value, date) content_type=ct, value=value, date=self.get_current_date())
def execute(self, server): def execute(self, server):
log = super(MonitorBackend, self).execute(server) log = super(ServiceMonitor, self).execute(server)
self.store(log)
return log return log

View File

@ -21,7 +21,6 @@ class ResourceForm(forms.ModelForm):
super(ResourceForm, self).__init__(*args, **kwargs) super(ResourceForm, self).__init__(*args, **kwargs)
if self.resource: if self.resource:
self.fields['verbose_name'].initial = self.resource.verbose_name self.fields['verbose_name'].initial = self.resource.verbose_name
self.fields['used'].initial = self.resource.get_used() # TODO
if self.resource.ondemand: if self.resource.ondemand:
self.fields['allocated'].required = False self.fields['allocated'].required = False
self.fields['allocated'].widget = ReadOnlyWidget(None, '') self.fields['allocated'].widget = ReadOnlyWidget(None, '')

View File

@ -1,7 +1,7 @@
import datetime import datetime
from django.db import models from django.db import models
from django.contrib.contenttypes import generic from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import validators from django.core import validators
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -34,17 +34,22 @@ class Resource(models.Model):
validators=[validators.RegexValidator(r'^[a-z0-9_\-]+$', validators=[validators.RegexValidator(r'^[a-z0-9_\-]+$',
_('Enter a valid name.'), 'invalid')]) _('Enter a valid name.'), 'invalid')])
verbose_name = models.CharField(_("verbose name"), max_length=256, unique=True) verbose_name = models.CharField(_("verbose name"), max_length=256, unique=True)
content_type = models.ForeignKey(ContentType) # TODO filter by servicE? content_type = models.ForeignKey(ContentType,
period = models.CharField(_("period"), max_length=16, choices=PERIODS, help_text=_("Model where this resource will be hooked"))
default=LAST) period = models.CharField(_("period"), max_length=16, choices=PERIODS, default=LAST,
ondemand = models.BooleanField(_("on demand"), default=False) 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"), default_allocation = models.PositiveIntegerField(_("default allocation"),
help_text=_("Default allocation value used when this is not an "
"on demand resource"),
null=True, blank=True) null=True, blank=True)
is_active = models.BooleanField(_("is active"), default=True) is_active = models.BooleanField(_("is active"), default=True)
disable_trigger = models.BooleanField(_("disable trigger"), default=False) 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"), crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"),
help_text=_("Crontab for periodic execution")) help_text=_("Crontab for periodic execution"))
# TODO create custom field that returns backend python objects
monitors = MultiSelectField(_("monitors"), max_length=256, monitors = MultiSelectField(_("monitors"), max_length=256,
choices=ServiceMonitor.get_choices()) choices=ServiceMonitor.get_choices())
@ -58,10 +63,13 @@ class Resource(models.Model):
try: try:
task = PeriodicTask.objects.get(name=name) task = PeriodicTask.objects.get(name=name)
except PeriodicTask.DoesNotExist: except PeriodicTask.DoesNotExist:
if self.is_active:
PeriodicTask.objects.create(name=name, task='resources.Monitor', PeriodicTask.objects.create(name=name, task='resources.Monitor',
args=[self.pk], crontab=self.crontab) args=[self.pk], crontab=self.crontab)
else: else:
if task.crontab != self.crontab: if not self.is_active:
task.delete()
elif task.crontab != self.crontab:
task.crontab = self.crontab task.crontab = self.crontab
task.save() task.save()
@ -97,7 +105,7 @@ class ResourceData(models.Model):
last_update = models.DateTimeField(null=True) last_update = models.DateTimeField(null=True)
allocated = models.PositiveIntegerField(null=True) allocated = models.PositiveIntegerField(null=True)
content_object = generic.GenericForeignKey() content_object = GenericForeignKey()
class Meta: class Meta:
unique_together = ('resource', 'content_type', 'object_id') unique_together = ('resource', 'content_type', 'object_id')
@ -159,7 +167,7 @@ class MonitorData(models.Model):
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
value = models.PositiveIntegerField() value = models.PositiveIntegerField()
content_object = generic.GenericForeignKey() content_object = GenericForeignKey()
class Meta: class Meta:
get_latest_by = 'date' get_latest_by = 'date'
@ -170,7 +178,7 @@ class MonitorData(models.Model):
def create_resource_relation(): def create_resource_relation():
relation = generic.GenericRelation('resources.ResourceData') relation = GenericRelation('resources.ResourceData')
for resources in Resource.group_by_content_type(): for resources in Resource.group_by_content_type():
model = resources[0].content_type.model_class() model = resources[0].content_type.model_class()
model.add_to_class('resources', relation) model.add_to_class('resources', relation)

View File

@ -7,6 +7,9 @@ from .models import Resource, ResourceData
class ResourceSerializer(serializers.ModelSerializer): class ResourceSerializer(serializers.ModelSerializer):
# TODO required allocation serializers (like resource form)
# TODO create missing ResourceData (like resource form)
# TODO make default allocation available on OPTIONS (like resource form)
name = serializers.SerializerMethodField('get_name') name = serializers.SerializerMethodField('get_name')
class Meta: class Meta:

View File

@ -12,6 +12,7 @@ from . import settings
class MailSystemUserBackend(ServiceController): class MailSystemUserBackend(ServiceController):
verbose_name = _("Mail system user") verbose_name = _("Mail system user")
model = 'mail.Mailbox' model = 'mail.Mailbox'
# TODO related_models = ('resources__content_type') ??
DEFAULT_GROUP = 'postfix' DEFAULT_GROUP = 'postfix'

View File

@ -185,8 +185,8 @@ class Apache2Traffic(ServiceMonitor):
context = self.get_context(site) context = self.get_context(site)
self.append(""" self.append("""
awk 'BEGIN { awk 'BEGIN {
ini = "%(start_date)s"; ini = "%(last_date)s";
end = "%(end_date)s"; end = "%(current_date)s";
months["Jan"] = "01"; months["Jan"] = "01";
months["Feb"] = "02"; months["Feb"] = "02";
@ -218,14 +218,15 @@ class Apache2Traffic(ServiceMonitor):
print sum; print sum;
}' %(log_file)s | { }' %(log_file)s | {
read value read value
echo %(site_name)s $value echo %(site_id)s $value
} }
""" % context) """ % context)
def get_context(self, site): def get_context(self, site):
# TODO log timezone!!
return { return {
'log_file': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name), 'log_file': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name),
'start_date': '', 'last_date': self.get_last_date(site).strftime("%Y%m%d%H%M%S"),
'end_date': '', 'current_date': self.get_current_date().strftime("%Y%m%d%H%M%S"),
'site_name': '', 'site_id': site.pk,
} }

View File

@ -52,6 +52,12 @@ class MultiSelectField(models.CharField):
return [ value for value,__ in arr_choices ] return [ value for value,__ in arr_choices ]
class NullableCharField(models.CharField):
def get_db_prep_value(self, value, connection=None, prepared=False):
return value or None
if isinstalled('south'): if isinstalled('south'):
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^orchestra\.models\.fields\.MultiSelectField"]) add_introspection_rules([], ["^orchestra\.models\.fields\.MultiSelectField"])
add_introspection_rules([], ["^orchestra\.models\.fields\.NullableCharField"])