diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index e3d1d233..40239d8c 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -29,10 +29,12 @@ class MailmanTraffic(ServiceMonitor): "echo %(object_id)s $(( ${SIZE}*${SUBSCRIBERS} ))" % context) def get_context(self, mail_list): + last_date = timezone.localtime(self.get_last_date(mail_list.pk)) + current_date = timezone.localtime(self.current_date) return { 'mailman_log': settings.LISTS_MAILMAN_POST_LOG_PATH, 'list_name': mail_list.name, 'object_id': mail_list.pk, - 'last_date': timezone.localtime(self.get_last_date(mail_list)).strftime("%b %d %H:%M:%S"), - 'current_date': timezone.localtime(self.get_current_date()).strftime("%b %d %H:%M:%S"), + 'last_date': last_date.strftime("%b %d %H:%M:%S"), + 'current_date': current_date.strftime("%b %d %H:%M:%S"), } diff --git a/orchestra/apps/orchestration/admin.py b/orchestra/apps/orchestration/admin.py index 72d93b1c..cf5e39ad 100644 --- a/orchestra/apps/orchestration/admin.py +++ b/orchestra/apps/orchestration/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from django.core.urlresolvers import reverse from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ +from djcelery.humanize import naturaldate from orchestra.admin.html import monospace_format from orchestra.admin.utils import link @@ -30,7 +31,7 @@ class RouteAdmin(admin.ModelAdmin): def display_model(self, route): try: - return route.backend_class().model + return escape(route.backend_class().model) except KeyError: return "NOT AVAILABLE" display_model.short_description = _("model") @@ -61,7 +62,9 @@ class BackendOperationInline(admin.TabularInline): try: return link('instance')(self, operation) except: - return _("deleted %s %s") % (operation.content_type, operation.object_id) + return _("deleted {0} {1}").format( + escape(operation.content_type), escape(operation.object_id) + ) instance_link.allow_tags = True instance_link.short_description = _("Instance") @@ -71,8 +74,8 @@ class BackendOperationInline(admin.TabularInline): class BackendLogAdmin(admin.ModelAdmin): list_display = ( - 'id', 'backend', 'server_link', 'display_state', 'exit_code', 'created', - 'execution_time', + 'id', 'backend', 'server_link', 'display_state', 'exit_code', + 'display_created', 'execution_time', ) list_display_links = ('id', 'backend') list_filter = ('state', 'backend') @@ -80,14 +83,10 @@ class BackendLogAdmin(admin.ModelAdmin): inlines = [BackendOperationInline] fields = [ 'backend', 'server', 'state', 'mono_script', 'mono_stdout', 'mono_stderr', - 'mono_traceback', 'exit_code', 'task_id', 'created', 'last_update', - 'execution_time' - ] - readonly_fields = [ - 'backend', 'server', 'state', 'mono_script', 'mono_stdout', 'mono_stderr', - 'mono_traceback', 'exit_code', 'task_id', 'created', 'last_update', - 'execution_time' + 'mono_traceback', 'exit_code', 'task_id', 'display_created', + 'display_last_update', 'execution_time' ] + readonly_fields = fields def server_link(self, log): url = reverse('admin:orchestration_server_change', args=(log.server.pk,)) @@ -118,6 +117,20 @@ class BackendLogAdmin(admin.ModelAdmin): return monospace_format(escape(log.traceback)) mono_traceback.short_description = _("traceback") + def display_last_update(self, log): + return '
{1}
'.format( + escape(str(log.last_update)), escape(naturaldate(log.last_update)), + ) + display_last_update.short_description = _("last update") + display_last_update.allow_tags = True + + def display_created(self, log): + return '
{1}
'.format( + escape(str(log.created)), escape(naturaldate(log.created)), + ) + display_created.short_description = _("created") + display_created.allow_tags = True + def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(BackendLogAdmin, self).get_queryset(request) diff --git a/orchestra/apps/orchestration/backends.py b/orchestra/apps/orchestration/backends.py index 7e71fbef..b1f6de6a 100644 --- a/orchestra/apps/orchestration/backends.py +++ b/orchestra/apps/orchestration/backends.py @@ -31,7 +31,7 @@ class ServiceBackend(object): def __unicode__(self): return type(self).__name__ - + def __str__(self): return unicode(self) diff --git a/orchestra/apps/orchestration/methods.py b/orchestra/apps/orchestration/methods.py index 2ca95b7a..fce32c95 100644 --- a/orchestra/apps/orchestration/methods.py +++ b/orchestra/apps/orchestration/methods.py @@ -13,14 +13,14 @@ from . import settings def BashSSH(backend, log, server, cmds): from .models import BackendLog - script = '\n\n'.join(['set -e'] + cmds + ['exit 0']) + script = '\n\n'.join(['set -e', 'set -o pipefail'] + cmds + ['exit 0']) script = script.replace('\r', '') log.script = script log.save() try: - # In order to avoid "Argument list too long" we while generate first a - # script file, then scp the escript and safely execute in remote + # Avoid "Argument list too long" on large scripts by genereting a file + # and scping it to the remote server digest = hashlib.md5(script).hexdigest() path = os.path.join(settings.ORCHESTRATION_TEMP_SCRIPT_PATH, digest) with open(path, 'w') as script_file: diff --git a/orchestra/apps/resources/admin.py b/orchestra/apps/resources/admin.py index 53a45cac..ea4aa9b8 100644 --- a/orchestra/apps/resources/admin.py +++ b/orchestra/apps/resources/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin, messages from django.contrib.contenttypes import generic from django.utils.functional import cached_property +from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ +from djcelery.humanize import naturaldate from orchestra.admin import ExtendedModelAdmin from orchestra.admin.filters import UsedContentTypeFilter @@ -100,6 +102,8 @@ def resource_inline_factory(resources): form = ResourceForm formset = ResourceInlineFormSet can_delete = False + fields = ('verbose_name', 'used', 'display_last_update', 'allocated',) + readonly_fields = ('used', 'display_last_update',) class Media: css = { @@ -109,6 +113,13 @@ def resource_inline_factory(resources): def has_add_permission(self, *args, **kwargs): """ Hidde add another """ return False + + def display_last_update(self, log): + return '
{1}
'.format( + escape(str(log.last_update)), escape(naturaldate(log.last_update)), + ) + display_last_update.short_description = _("last update") + display_last_update.allow_tags = True return ResourceInline diff --git a/orchestra/apps/resources/backends.py b/orchestra/apps/resources/backends.py index 6339f316..686289dd 100644 --- a/orchestra/apps/resources/backends.py +++ b/orchestra/apps/resources/backends.py @@ -2,9 +2,9 @@ import datetime from django.contrib.contenttypes.models import ContentType from django.utils import timezone +from django.utils.functional import cached_property from orchestra.apps.orchestration import ServiceBackend -from orchestra.utils.functional import cached class ServiceMonitor(ServiceBackend): @@ -23,18 +23,29 @@ class ServiceMonitor(ServiceBackend): if backend != ServiceMonitor and ServiceMonitor in backend.__mro__: yield backend - @cached - def get_last_date(self, obj): + @cached_property + def current_date(self): + return timezone.now() + + @cached_property + def content_type(self): + app_label, model = self.model.split('.') + model = model.lower() + return ContentType.objects.get(app_label=app_label, model=model) + + def get_last_data(self, object_id): from .models import MonitorData try: - ct = ContentType.objects.get_for_model(type(obj)) - return MonitorData.objects.filter(content_type=ct, object_id=obj.pk).latest().date + return MonitorData.objects.filter(content_type=self.content_type, + object_id=object_id).latest() except MonitorData.DoesNotExist: - return self.get_current_date() - datetime.timedelta(days=1) - - @cached - def get_current_date(self): - return timezone.now() + return None + + def get_last_date(self, object_id): + data = self.get_last_data(object_id) + if data is None: + return self.current_date - datetime.timedelta(days=1) + return data.date def store(self, log): """ object_id value """ @@ -46,7 +57,7 @@ class ServiceMonitor(ServiceBackend): line = line.strip() object_id, value = line.split() MonitorData.objects.create(monitor=name, object_id=object_id, - content_type=ct, value=value, date=self.get_current_date()) + content_type=ct, value=value, date=self.current_date) def execute(self, server): log = super(ServiceMonitor, self).execute(server) diff --git a/orchestra/apps/resources/forms.py b/orchestra/apps/resources/forms.py index fc46c8a4..44cf9adf 100644 --- a/orchestra/apps/resources/forms.py +++ b/orchestra/apps/resources/forms.py @@ -1,5 +1,7 @@ from django import forms +from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ +from djcelery.humanize import naturaldate from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget @@ -9,8 +11,6 @@ class ResourceForm(forms.ModelForm): required=False) used = forms.IntegerField(label=_("Used"), widget=ShowTextWidget(), required=False) - last_update = forms.DateTimeField(label=_("Last update"), widget=ShowTextWidget(), - required=False) allocated = forms.IntegerField(label=_("Allocated")) class Meta: diff --git a/orchestra/apps/users/backends.py b/orchestra/apps/users/backends.py index ee957fa1..c209c9f4 100644 --- a/orchestra/apps/users/backends.py +++ b/orchestra/apps/users/backends.py @@ -101,10 +101,12 @@ class FTPTraffic(ServiceMonitor): ' | xargs echo %(object_id)s """ % context) def get_context(self, user): + last_date = timezone.localtime(self.get_last_date(user.pk)) + current_date = timezone.localtime(self.current_date) return { 'log_file': settings.USERS_FTP_LOG_PATH, - 'last_date': timezone.localtime(self.get_last_date(user)).strftime("%Y%m%d%H%M%S"), - 'current_date': timezone.localtime(self.get_current_date()).strftime("%Y%m%d%H%M%S"), + 'last_date': last_date.strftime("%Y%m%d%H%M%S"), + 'current_date': current_date.strftime("%Y%m%d%H%M%S"), 'object_id': user.pk, 'username': user.username, } diff --git a/orchestra/apps/vps/backends.py b/orchestra/apps/vps/backends.py index f38b3384..4b077bda 100644 --- a/orchestra/apps/vps/backends.py +++ b/orchestra/apps/vps/backends.py @@ -3,18 +3,32 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.apps.resources import ServiceMonitor -class OpenVZDisk(ServiceMonitor): - model = 'vps.VPS' - resource = ServiceMonitor.DISK - - -class OpenVZMemory(ServiceMonitor): - model = 'vps.VPS' - resource = ServiceMonitor.MEMORY - - class OpenVZTraffic(ServiceMonitor): model = 'vps.VPS' resource = ServiceMonitor.TRAFFIC - - + + def process(self, line, obj): + """ diff with last stored value """ + object_id, value = line.split() + last = self.get_last_data(object_id) + if not last or last.value > value: + return object_id, value + return object_id, value-last.value + + def monitor(self, container): + """ Get OpenVZ container traffic on a Proxmox +2.0 cluster """ + context = self.get_context(container) + self.append( + "CONF=$(grep -r 'HOSTNAME=\"%(hostname)s\"' /etc/pve/nodes/*/openvz/*.conf)" % context) + self.append('NODE=$(echo "${CONF}" | cut -d"/" -f5)') + self.append('CTID=$(echo "${CONF}" | cut -d"/" -f7 | cur -d"\." -f1)') + self.append( + "ssh root@${NODE} vzctl exec ${CTID} cat /proc/net/dev \\\n" + " | grep venet0 \\\n" + " | awk -F: '{print $2}' \\\n" + " | awk '{print $1+$9}'") + + def get_context(self, container): + return { + 'hostname': container.hostname, + } diff --git a/orchestra/apps/websites/backends/apache.py b/orchestra/apps/websites/backends/apache.py index 5c751eda..a58ce5f2 100644 --- a/orchestra/apps/websites/backends/apache.py +++ b/orchestra/apps/websites/backends/apache.py @@ -219,9 +219,11 @@ class Apache2Traffic(ServiceMonitor): }' %(log_file)s || echo 0; } | xargs echo %(object_id)s """ % context) def get_context(self, site): + last_date = timezone.localtime(self.get_last_date(site.pk)) + current_date = timezone.localtime(self.current_date) return { 'log_file': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name), - 'last_date': timezone.localtime(self.get_last_date(site)).strftime("%Y%m%d%H%M%S"), - 'current_date': timezone.localtime(self.get_current_date()).strftime("%Y%m%d%H%M%S"), + 'last_date': last_date.strftime("%Y%m%d%H%M%S"), + 'current_date': current_date.strftime("%Y%m%d%H%M%S"), 'object_id': site.pk, } diff --git a/orchestra/utils/time.py b/orchestra/utils/time.py deleted file mode 100644 index f3cf5cf7..00000000 --- a/orchestra/utils/time.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import absolute_import - -from datetime import datetime - -from django.utils.timesince import timesince as django_timesince -from django.utils.timezone import is_aware, utc - - -# TODO deprecate in favour of celery timesince -def timesince(d, now=None, reversed=False): - """ Hack to provide second precision under 2 minutes """ - if not now: - now = datetime.now(utc if is_aware(d) else None) - - delta = (d - now) if reversed else (now - d) - s = django_timesince(d, now=now, reversed=reversed) - - if len(s.split(' ')) is 2: - count, name = s.split(' ') - if name in ['minutes', 'minute']: - seconds = delta.seconds % 60 - extension = '%(number)d %(type)s' % {'number': seconds, 'type': 'seconds'} - if int(count) is 0: - return extension - elif int(count) < 2: - s += ', %s' % extension - return s - - -def timeuntil(d, now=None): - """ - Like timesince, but returns a string measuring the time until - the given time. - """ - return timesince(d, now, reversed=True)