Implemented some monitors

This commit is contained in:
Marc 2014-07-11 14:48:46 +00:00
parent e38e70d539
commit c54c084dff
16 changed files with 168 additions and 85 deletions

View File

@ -49,3 +49,8 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* pip install pyinotify * pip install pyinotify
* create custom field that returns backend python objects * create custom field that returns backend python objects
* Timezone awareness on monitoring system (reading server-side logs with different TZ than orchestra) maybe a settings value? (use UTC internally, timezone.localtime() when interacting with servers)
* Resource metric: KB MB B?
* EMAIL backend operations which contain stderr messages (because under certain failures status code is still 0)

View File

@ -1,8 +1,8 @@
import datetime from django.utils import timezone
def generate_zone_serial(): def generate_zone_serial():
today = datetime.date.today() today = timezone.now()
return int("%.4d%.2d%.2d%.2d" % (today.year, today.month, today.day, 0)) return int("%.4d%.2d%.2d%.2d" % (today.year, today.month, today.day, 0))

View File

@ -1,9 +1,12 @@
from django.template import Template, Context from django.template import Template, Context
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor from orchestra.apps.resources import ServiceMonitor
from . import settings
class MailmanBackend(ServiceController): class MailmanBackend(ServiceController):
verbose_name = "Mailman" verbose_name = "Mailman"
@ -14,28 +17,22 @@ class MailmanTraffic(ServiceMonitor):
model = 'lists.List' model = 'lists.List'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
def process(self, output): def monitor(self, mail_list):
for line in output.readlines(): context = self.get_context(mail_list)
listname, value = line.strip().slpit() self.append(
"SUBSCRIBERS=$(list_members %(list_name)s | wc -l)\n"
"SIZE=$(grep ' post to %(list_name)s ' %(mailman_log)s \\\n"
" | awk '\"%(last_date)s\"<=$0 && $0<=\"%(current_date)s\"' \\\n"
" | sed 's/.*size=\([0-9]*\).*/\\1/' \\\n"
" | tr '\\n' '+' \\\n"
" | xargs -i echo {}0 )\n"
"echo %(object_id)s $(( ${SIZE}*${SUBSCRIBERS} ))" % context)
def monitor(self, mailinglist): def get_context(self, mail_list):
self.append( return {
"LISTS=$(grep -v 'post to mailman' /var/log/mailman/post" 'mailman_log': settings.LISTS_MAILMAN_POST_LOG_PATH,
" | grep size | cut -d'<' -f2 | cut -d'>' -f1 | sort | uniq" 'list_name': mail_list.name,
" | while read line; do \n" 'object_id': mail_list.pk,
" grep \"$line\" post | head -n1 | awk {'print $8\" \"$11'}" 'last_date': timezone.localtime(self.get_last_date(mail_list)).strftime("%b %d %H:%M:%S"),
" | sed 's/size=//' | sed 's/,//'\n" 'current_date': timezone.localtime(self.get_current_date()).strftime("%b %d %H:%M:%S"),
"done)" }
)
self.append(
'SUBS=""\n'
'while read LIST; do\n'
' NAME=$(echo "$LIST" | awk {\'print $1\'})\n'
' SIZE=$(echo "$LIST" | awk {\'print $2\'})\n'
' if [[ ! $(echo -e "$SUBS" | grep "$NAME") ]]; then\n'
' SUBS="${SUBS}${NAME} $(list_members "$NAME" | wc -l)\n"\n'
' fi\n'
' SUBSCRIBERS=$(echo -e "$SUBS" | grep "$NAME" | awk {\'print $2\'})\n'
' echo "$NAME $(($SUBSCRIBERS*$SIZE))"\n'
'done <<< "$LISTS"'
)

View File

@ -26,7 +26,7 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
help_text=_("Enter the same password as above, for verification.")) help_text=_("Enter the same password as above, for verification."))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ListAdminForm, self).__init__(*args, **kwargs) super(ListCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].validators.append(validate_password) self.fields['password1'].validators.append(validate_password)
def clean_password2(self): def clean_password2(self):
@ -38,7 +38,7 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
return password2 return password2
def save(self, commit=True): def save(self, commit=True):
obj = super(ListAdminForm, self).save(commit=commit) obj = super(ListCreationForm, self).save(commit=commit)
obj.set_password(self.cleaned_data["password1"]) obj.set_password(self.cleaned_data["password1"])
return obj return obj

View File

@ -6,3 +6,6 @@ from django.conf import settings
LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain') LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain')
LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN', 'grups.orchestra.lan') LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN', 'grups.orchestra.lan')
LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
'/var/log/mailman/post')

View File

@ -1,6 +1,6 @@
from datetime import datetime
from functools import partial from functools import partial
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins from orchestra.utils import plugins
@ -89,7 +89,7 @@ class ServiceBackend(object):
return sorted(choices, key=lambda e: e[0]) return sorted(choices, key=lambda e: e[0])
def get_banner(self): def get_banner(self):
time = datetime.now().strftime("%h %d, %Y %I:%M:%S") time = timezone.now().strftime("%h %d, %Y %I:%M:%S")
return "Generated by Orchestra %s" % time return "Generated by Orchestra %s" % time
def execute(self, server): def execute(self, server):

View File

@ -10,9 +10,9 @@ from .helpers import send_report
def as_task(execute): def as_task(execute):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
with db.transaction.commit_manually(): # with db.transaction.commit_manually():
log = execute(*args, **kwargs) log = execute(*args, **kwargs)
db.transaction.commit() # db.transaction.commit()
if log.state != log.SUCCESS: if log.state != log.SUCCESS:
send_report(execute, args, log) send_report(execute, args, log)
return log return log

View File

@ -1,6 +1,7 @@
import datetime import datetime
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from orchestra.apps.orchestration import ServiceBackend from orchestra.apps.orchestration import ServiceBackend
from orchestra.utils.functional import cached from orchestra.utils.functional import cached
@ -17,14 +18,14 @@ class ServiceMonitor(ServiceBackend):
@classmethod @classmethod
def get_backends(cls): def get_backends(cls):
""" filter monitor classes """ """ filter monitor classes """
return [plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__] for backend in cls.plugins:
if backend != ServiceMonitor and ServiceMonitor in backend.__mro__:
yield backend
@cached @cached
def get_last_date(self, obj): def get_last_date(self, obj):
from .models import MonitorData from .models import MonitorData
try: try:
# TODO replace
#return MonitorData.objects.filter(content_object=obj).latest().date
ct = ContentType.objects.get_for_model(type(obj)) 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=ct, object_id=obj.pk).latest().date
except MonitorData.DoesNotExist: except MonitorData.DoesNotExist:
@ -32,7 +33,7 @@ class ServiceMonitor(ServiceBackend):
@cached @cached
def get_current_date(self): def get_current_date(self):
return datetime.datetime.now() return timezone.now()
def store(self, log): def store(self, log):
""" object_id value """ """ object_id value """

View File

@ -36,22 +36,27 @@ class Resource(models.Model):
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, content_type = models.ForeignKey(ContentType,
help_text=_("Model where this resource will be hooked")) help_text=_("Model where this resource will be hooked"))
period = models.CharField(_("period"), max_length=16, choices=PERIODS, default=LAST, period = models.CharField(_("period"), max_length=16, choices=PERIODS,
help_text=_("Operation used for aggregating this resource monitored data.")) default=LAST,
help_text=_("Operation used for aggregating this resource monitored"
"data."))
ondemand = models.BooleanField(_("on demand"), default=False, ondemand = models.BooleanField(_("on demand"), default=False,
help_text=_("If enabled the resource will not be pre-allocated, " help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand")) "but allocated under the application demand"))
default_allocation = models.PositiveIntegerField(_("default allocation"), default_allocation = models.PositiveIntegerField(_("default allocation"),
null=True, blank=True,
help_text=_("Default allocation value used when this is not an " help_text=_("Default allocation value used when this is not an "
"on demand resource"), "on demand resource"))
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")) help_text=_("Disables monitors exeeded and recovery triggers"))
crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"), crontab = models.ForeignKey(CrontabSchedule, verbose_name=_("crontab"),
help_text=_("Crontab for periodic execution")) null=True, blank=True,
monitors = MultiSelectField(_("monitors"), max_length=256, help_text=_("Crontab for periodic execution. "
choices=ServiceMonitor.get_choices()) "Leave it empty to disable periodic monitoring"))
monitors = MultiSelectField(_("monitors"), max_length=256, blank=True,
choices=ServiceMonitor.get_choices(),
help_text=_("Monitor backends used for monitoring this resource."))
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -83,7 +88,8 @@ class Resource(models.Model):
def group_by_content_type(cls): def group_by_content_type(cls):
prev = None prev = None
group = [] group = []
for resource in cls.objects.filter(is_active=True).order_by('content_type'): resources = cls.objects.filter(is_active=True).order_by('content_type')
for resource in resources:
ct = resource.content_type ct = resource.content_type
if prev != ct: if prev != ct:
if group: if group:
@ -121,7 +127,7 @@ class ResourceData(models.Model):
def get_used(self): def get_used(self):
resource = self.resource resource = self.resource
today = datetime.date.today() today = timezone.now()
result = 0 result = 0
has_result = False has_result = False
for monitor in resource.monitors: for monitor in resource.monitors:
@ -133,7 +139,8 @@ class ResourceData(models.Model):
except MonitorData.DoesNotExist: except MonitorData.DoesNotExist:
continue continue
has_result = True has_result = True
epoch = datetime(year=today.year, month=today.month, day=1) epoch = datetime(year=today.year, month=today.month, day=1,
tzinfo=timezone.utc)
total = (epoch-last.date).total_seconds() total = (epoch-last.date).total_seconds()
dataset = dataset.filter(date__year=today.year, dataset = dataset.filter(date__year=today.year,
date__month=today.month) date__month=today.month)
@ -154,7 +161,8 @@ class ResourceData(models.Model):
continue continue
has_result = True has_result = True
else: else:
raise NotImplementedError("%s support not implemented" % self.period) msg = "%s support not implemented" % self.period
raise NotImplementedError(msg)
return result if has_result else None return result if has_result else None

View File

@ -24,6 +24,7 @@ class ResourceSerializer(serializers.ModelSerializer):
# Monkey-patching section # Monkey-patching section
if not running_syncdb(): if not running_syncdb():
# TODO why this is even loaded during syncdb?
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()
router.insert(model, 'resources', ResourceSerializer, required=False, many=True) router.insert(model, 'resources', ResourceSerializer, required=False, many=True)
@ -54,7 +55,7 @@ if not running_syncdb():
old_metadata = viewset.metadata old_metadata = viewset.metadata
def metadata(self, request, resources=resources): def metadata(self, request, resources=resources):
""" Display resource configuration """ """ Provides available resources description """
ret = old_metadata(self, request) ret = old_metadata(self, request)
ret['available_resources'] = [ ret['available_resources'] = [
{ {

View File

@ -1,5 +1,5 @@
from celery import shared_task from celery import shared_task
from django.utils import timezone
from orchestra.apps.orchestration.models import BackendOperation as Operation from orchestra.apps.orchestration.models import BackendOperation as Operation
from .backends import ServiceMonitor from .backends import ServiceMonitor
@ -26,6 +26,7 @@ def monitor(resource_id):
for obj in model.objects.all(): for obj in model.objects.all():
data = MonitorData.get_or_create(obj, resource) data = MonitorData.get_or_create(obj, resource)
current = data.get_used() current = data.get_used()
if not resource.disable_trigger:
if data.used < data.allocated and current > data.allocated: if data.used < data.allocated and current > data.allocated:
op = Operation.create(backend, data.content_object, Operation.EXCEED) op = Operation.create(backend, data.content_object, Operation.EXCEED)
operations.append(op) operations.append(op)
@ -33,6 +34,6 @@ def monitor(resource_id):
op = Operation.create(backend, data.content_object, Operation.RECOVERY) op = Operation.create(backend, data.content_object, Operation.RECOVERY)
operation.append(op) operation.append(op)
data.used = current data.used = current
data.las_update = datetime.now() data.las_update = timezone.now()
data.save() data.save()
Operation.execute(operations) Operation.execute(operations)

View File

@ -1,3 +1,4 @@
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController from orchestra.apps.orchestration import ServiceController
@ -49,13 +50,62 @@ class SystemUserDisk(ServiceMonitor):
def monitor(self, user): def monitor(self, user):
context = self.get_context(user) context = self.get_context(user)
self.append("du -s %(home)s | {\n" self.append("du -s %(home)s | xargs echo %(object_id)s" % context)
" read value\n"
" echo '%(username)s' $value\n" def get_context(self, user):
"}" % context) context = SystemUserBackend().get_context(user)
context['object_id'] = user.pk
return context
class FTPTraffic(ServiceMonitor):
model = 'users.User'
resource = ServiceMonitor.TRAFFIC
verbose_name = _('FTP traffic')
def monitor(self, user):
context = self.get_context(user)
self.append("""
grep "UPLOAD\|DOWNLOAD" %(log_file)s | grep " \\[%(username)s\\] " | awk '
BEGIN {
ini = "%(last_date)s"
end = "%(current_date)s"
months["Jan"] = "01"
months["Feb"] = "02"
months["Mar"] = "03"
months["Apr"] = "04"
months["May"] = "05"
months["Jun"] = "06"
months["Jul"] = "07"
months["Aug"] = "08"
months["Sep"] = "09"
months["Oct"] = "10"
months["Nov"] = "11"
months["Dec"] = "12"
} {
# log: Fri Jul 11 13:23:17 2014
split($4, t, ":")
# line_date = year month day hour minute second
line_date = $5 months[$2] $3 t[1] t[2] t[3]
if ( line_date > ini && line_date < end)
split($0, l, "\\", ")
split(l[3], b, " ")
sum += b[1]
} END {
if ( sum )
print sum
else
print 0
}
' | xargs echo %(object_id)s """ % context)
def get_context(self, user):
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"),
'object_id': user.pk,
'username': user.username,
}
def process(self, output):
# TODO transaction
for line in output.readlines():
username, value = line.strip().slpit()
History.store(object_id=user_id, value=value)

View File

@ -29,10 +29,10 @@ class MailSystemUserBackend(ServiceController):
self.append("chown %(username)s.%(group)s %(home)s" % context) self.append("chown %(username)s.%(group)s %(home)s" % context)
def generate_filter(self, mailbox, context): def generate_filter(self, mailbox, context):
datetime = timezone.now().strftime("%B %d, %Y, %H:%M") now = timezone.now().strftime("%B %d, %Y, %H:%M")
context['filtering'] = ( context['filtering'] = (
"# Sieve Filter\n" "# Sieve Filter\n"
"# Generated by Orchestra %s\n\n" % datetime "# Generated by Orchestra %s\n\n" % now
) )
if mailbox.use_custom_filtering: if mailbox.use_custom_filtering:
context['filtering'] += mailbox.custom_filtering context['filtering'] += mailbox.custom_filtering
@ -139,7 +139,21 @@ class AutoresponseBackend(ServiceController):
model = 'mail.Autoresponse' model = 'mail.Autoresponse'
class MailDisk(ServiceMonitor): class MaildirDisk(ServiceMonitor):
model = 'email.Mailbox' model = 'email.Mailbox'
resource = ServiceMonitor.DISK resource = ServiceMonitor.DISK
verbose_name = _("Mail disk") verbose_name = _("Maildir disk usage")
def monitor(self, mailbox):
context = self.get_context(mailbox)
self.append(
"SIZE=$(sed -n '2p' %(maildir_path)s | cut -d' ' -f1)\n"
"echo %(object_id)s ${SIZE:-0}" % context
)
def get_context(self, mailbox):
context = MailSystemUserBackend().get_context(site)
context['home'] = settings.EMAILS_HOME % context
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['object_id'] = mailbox.pk
return context

View File

@ -2,3 +2,5 @@ from django.conf import settings
USERS_SYSTEMUSER_HOME = getattr(settings, 'USERES_SYSTEMUSER_HOME', '/home/%(username)s') USERS_SYSTEMUSER_HOME = getattr(settings, 'USERES_SYSTEMUSER_HOME', '/home/%(username)s')
USERS_FTP_LOG_PATH = getattr(settings, 'USERS_FTP_LOG_PATH', '/var/log/vsftpd.log')

View File

@ -1,6 +1,7 @@
import os import os
from django.template import Template, Context from django.template import Template, Context
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController from orchestra.apps.orchestration import ServiceController
@ -185,8 +186,8 @@ class Apache2Traffic(ServiceMonitor):
context = self.get_context(site) context = self.get_context(site)
self.append(""" self.append("""
awk 'BEGIN { awk 'BEGIN {
ini = "%(last_date)s"; ini = "%(last_date)s"
end = "%(current_date)s"; end = "%(current_date)s"
months["Jan"] = "01"; months["Jan"] = "01";
months["Feb"] = "02"; months["Feb"] = "02";
@ -201,32 +202,31 @@ class Apache2Traffic(ServiceMonitor):
months["Nov"] = "11"; months["Nov"] = "11";
months["Dec"] = "12"; months["Dec"] = "12";
} { } {
# date = [11/Jul/2014:13:50:41
date = substr($4, 2) date = substr($4, 2)
year = substr(date, 8, 4) year = substr(date, 8, 4)
month = months[substr(date, 4, 3)]; month = months[substr(date, 4, 3)];
day = substr(date, 1, 2) day = substr(date, 1, 2)
hour = substr(date, 13, 2) hour = substr(date, 13, 2)
minute = substr(date, 16, 2) minute = substr(date, 16, 2)
second = substr(date, 19, 2); second = substr(date, 19, 2)
line_date = year month day hour minute second line_date = year month day hour minute second
if ( line_date > ini && line_date < end) if ( line_date > ini && line_date < end)
if ( $10 == "" ) if ( $10 == "" )
sum += $9 sum += $9
else else
sum += $10; sum += $10
} END { } END {
print sum; if ( sum )
}' %(log_file)s | { print sum
read value else
echo %(site_id)s $value print 0
} }' %(log_file)s | xargs echo %(object_id)s """ % 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),
'last_date': self.get_last_date(site).strftime("%Y%m%d%H%M%S"), 'last_date': timezone.localtime(self.get_last_date(site)).strftime("%Y%m%d%H%M%S"),
'current_date': self.get_current_date().strftime("%Y%m%d%H%M%S"), 'current_date': timezone.localtime(self.get_current_date()).strftime("%Y%m%d%H%M%S"),
'site_id': site.pk, 'object_id': site.pk,
} }

View File

@ -6,6 +6,7 @@ from django.utils.timesince import timesince as django_timesince
from django.utils.timezone import is_aware, utc from django.utils.timezone import is_aware, utc
# TODO deprecate in favour of celery timesince
def timesince(d, now=None, reversed=False): def timesince(d, now=None, reversed=False):
""" Hack to provide second precision under 2 minutes """ """ Hack to provide second precision under 2 minutes """
if not now: if not now: