Fixes on resources

This commit is contained in:
Marc Aymerich 2014-10-27 13:29:02 +00:00
parent 3b4b69e9a2
commit d3727f0565
27 changed files with 184 additions and 77 deletions

View File

@ -180,4 +180,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* Subdomain saving should not trigger bind slave * Subdomain saving should not trigger bind slave
* Domain account change, unselected checkbox: migrate subdomains * multiple files monitoring
* prevent adding local email addresses on account.contacts account.email
* Resource monitoring without ROUTE alert or explicit error

View File

@ -10,7 +10,7 @@ from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.utils import apps from orchestra.utils import apps
from .actions import view_zone from .actions import view_zone
from .forms import RecordInlineFormSet, DomainAdminForm from .forms import RecordInlineFormSet, CreateDomainAdminForm
from .filters import TopDomainListFilter from .filters import TopDomainListFilter
from .models import Domain, Record from .models import Domain, Record
@ -54,11 +54,11 @@ class DomainInline(admin.TabularInline):
class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin): class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin):
# TODO name link
fields = ('name', ('account', 'migrate_subdomains'),)
list_display = ( list_display = (
'structured_name', 'display_is_top', 'websites', 'account_link' 'structured_name', 'display_is_top', 'websites', 'account_link'
) )
add_fields = ('name', 'account')
fields = ('name', 'account_link')
inlines = [RecordInline, DomainInline] inlines = [RecordInline, DomainInline]
list_filter = [TopDomainListFilter] list_filter = [TopDomainListFilter]
change_readonly_fields = ('name',) change_readonly_fields = ('name',)
@ -66,7 +66,7 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
default_changelist_filters = ( default_changelist_filters = (
('top_domain', 'True'), ('top_domain', 'True'),
) )
form = DomainAdminForm add_form = CreateDomainAdminForm
change_view_actions = [view_zone] change_view_actions = [view_zone]
def structured_name(self, domain): def structured_name(self, domain):
@ -112,11 +112,11 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
qs = qs.prefetch_related('websites') qs = qs.prefetch_related('websites')
return qs return qs
def save_related(self, request, form, formsets, change): # def save_related(self, request, form, formsets, change):
super(DomainAdmin, self).save_related(request, form, formsets, change) # super(DomainAdmin, self).save_related(request, form, formsets, change)
if form.cleaned_data['migrate_subdomains']: # if form.cleaned_data['migrate_subdomains']:
domain = form.instance # domain = form.instance
domain.subdomains.update(account_id=domain.account_id) # domain.subdomains.update(account_id=domain.account_id)
admin.site.register(Domain, DomainAdmin) admin.site.register(Domain, DomainAdmin)

View File

@ -86,12 +86,14 @@ class Bind9MasterDomainBackend(ServiceController):
return self.get_servers(domain, Bind9SlaveDomainBackend) return self.get_servers(domain, Bind9SlaveDomainBackend)
def get_context(self, domain): def get_context(self, domain):
slaves = self.get_slaves(domain)
context = { context = {
'name': domain.name, 'name': domain.name,
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name}, 'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
'subdomains': domain.subdomains.all(), 'subdomains': domain.subdomains.all(),
'banner': self.get_banner(), 'banner': self.get_banner(),
'slaves': '; '.join(self.get_slaves(domain)) or 'none', 'slaves': '; '.join(slaves) or 'none',
'also_notify': '; '.join(slaves) + ';' if slaves else '',
} }
context.update({ context.update({
'conf_path': settings.DOMAINS_MASTERS_PATH, 'conf_path': settings.DOMAINS_MASTERS_PATH,
@ -101,6 +103,7 @@ class Bind9MasterDomainBackend(ServiceController):
type master; type master;
file "%(zone_path)s"; file "%(zone_path)s";
allow-transfer { %(slaves)s; }; allow-transfer { %(slaves)s; };
also-notify { %(also_notify)s };
notify yes; notify yes;
};""" % context) };""" % context)
}) })

View File

@ -7,13 +7,13 @@ from .helpers import domain_for_validation
from .models import Domain from .models import Domain
class DomainAdminForm(forms.ModelForm): class CreateDomainAdminForm(forms.ModelForm):
migrate_subdomains = forms.BooleanField(label=_("Migrate subdomains"), required=False, # migrate_subdomains = forms.BooleanField(label=_("Migrate subdomains"), required=False,
initial=False, help_text=_("Propagate the account owner change to subdomains.")) # initial=False, help_text=_("Propagate the account owner change to subdomains."))
def clean(self): def clean(self):
""" inherit related top domain account, when exists """ """ inherit related top domain account, when exists """
cleaned_data = super(DomainAdminForm, self).clean() cleaned_data = super(CreateDomainAdminForm, self).clean()
if not cleaned_data['account']: if not cleaned_data['account']:
domain = Domain(name=cleaned_data['name']) domain = Domain(name=cleaned_data['name'])
top = domain.get_top() top = domain.get_top()

View File

@ -182,6 +182,7 @@ class Record(models.Model):
def clean(self): def clean(self):
""" validates record value based on its type """ """ validates record value based on its type """
# validate value # validate value
self.value = self.value.lower().strip()
mapp = { mapp = {
self.MX: validators.validate_mx_record, self.MX: validators.validate_mx_record,
self.NS: validators.validate_zone_label, self.NS: validators.validate_zone_label,

View File

@ -12,12 +12,11 @@ from django.utils.translation import ugettext_lazy as _
from markdown import markdown from markdown import markdown
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin#, ChangeViewActions
from orchestra.admin.utils import (admin_link, admin_colored, wrap_admin_view, from orchestra.admin.utils import admin_link, admin_colored, wrap_admin_view, admin_date
admin_date) from orchestra.apps.contacts.models import Contact
from orchestra.apps.contacts import settings as contacts_settings
from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets, from .actions import (reject_tickets, resolve_tickets, take_tickets, close_tickets,
mark_as_unread, mark_as_read, set_default_queue) mark_as_unread, mark_as_read, set_default_queue)
from .filters import MyTicketsListFilter, TicketStateListFilter from .filters import MyTicketsListFilter, TicketStateListFilter
from .forms import MessageInlineForm, TicketForm from .forms import MessageInlineForm, TicketForm
from .helpers import get_ticket_changes, markdown_formated_changes, filter_actions from .helpers import get_ticket_changes, markdown_formated_changes, filter_actions
@ -311,7 +310,7 @@ class QueueAdmin(admin.ModelAdmin):
def get_list_display(self, request): def get_list_display(self, request):
""" show notifications """ """ show notifications """
list_display = list(self.list_display) list_display = list(self.list_display)
for value, verbose in contacts_settings.CONTACTS_EMAIL_USAGES: for value, verbose in Contact.EMAIL_USAGES:
def display_notify(queue, notify=value): def display_notify(queue, notify=value):
return notify in queue.notify return notify in queue.notify
display_notify.short_description = verbose display_notify.short_description = verbose

View File

@ -102,7 +102,8 @@ class MailmanBackend(ServiceController):
for address in self.addresses: for address in self.addresses:
context['address'] = address context['address'] = address
self.append('sed -i "s/^.*\s%(name)s%(address)s\s*$//" %(virtual_alias)s' % context) self.append('sed -i "s/^.*\s%(name)s%(address)s\s*$//" %(virtual_alias)s' % context)
self.append("rmlist -a %(name)s" % context) # TODO remove
self.append("echo rmlist -a %(name)s" % context)
def commit(self): def commit(self):
context = self.get_context_files() context = self.get_context_files()
@ -172,7 +173,7 @@ class MailmanTraffic(ServiceMonitor):
} }
class MailmanTraffic(ServiceMonitor): class MailmanSubscribers(ServiceMonitor):
model = 'lists.List' model = 'lists.List'
verbose_name = _("Mailman subscribers") verbose_name = _("Mailman subscribers")

View File

@ -1,6 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra import settings as orchestra_settings
from orchestra.core.validators import validate_password from orchestra.core.validators import validate_password
from orchestra.forms.widgets import ReadOnlyWidget from orchestra.forms.widgets import ReadOnlyWidget
@ -16,7 +17,7 @@ class CleanAddressMixin(object):
class ListCreationForm(CleanAddressMixin, forms.ModelForm): class ListCreationForm(CleanAddressMixin, forms.ModelForm):
password1 = forms.CharField(label=_("Password"), password1 = forms.CharField(label=_("Password"), validators=[validate_password],
widget=forms.PasswordInput) widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput, widget=forms.PasswordInput,
@ -24,7 +25,13 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ListCreationForm, self).__init__(*args, **kwargs) super(ListCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].validators.append(validate_password) if orchestra_settings.ORCHESTRA_MIGRATION_MODE:
self.fields['password1'].widget = forms.HiddenInput()
self.fields['password1'].required = False
self.fields['password2'].widget = forms.HiddenInput()
self.fields['password2'].required = False
self.fields['admin_email'].widget = forms.HiddenInput()
self.fields['admin_email'].required = False
def clean_password2(self): def clean_password2(self):
password1 = self.cleaned_data.get("password1") password1 = self.cleaned_data.get("password1")
@ -36,7 +43,8 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
def save(self, commit=True): def save(self, commit=True):
obj = super(ListCreationForm, self).save(commit=commit) obj = super(ListCreationForm, self).save(commit=commit)
obj.set_password(self.cleaned_data["password1"]) if not orchestra_settings.ORCHESTRA_MIGRATION_MODE:
obj.set_password(self.cleaned_data["password1"])
return obj return obj

View File

@ -46,8 +46,8 @@ class PasswdVirtualUserBackend(ServiceController):
fi""" % context)) fi""" % context))
def generate_filter(self, mailbox, context): def generate_filter(self, mailbox, context):
self.append("doveadm mailbox create -u %(username)s Spam" % context) # TODO override webmail filters??? self.append("doveadm mailbox create -u %(username)s Spam" % context)
context['filtering_path'] = os.path.join(context['home'], '.dovecot.sieve') context['filtering_path'] = settings.MAILBOXES_SIEVE_PATH % context
filtering = mailbox.get_filtering() filtering = mailbox.get_filtering()
if filtering: if filtering:
context['filtering'] = '# %(banner)s\n' + filtering context['filtering'] = '# %(banner)s\n' + filtering

View File

@ -14,7 +14,7 @@ class MailboxForm(forms.ModelForm):
# TODO keep track of this ticket for future reimplementation # TODO keep track of this ticket for future reimplementation
# https://code.djangoproject.com/ticket/897 # https://code.djangoproject.com/ticket/897
addresses = forms.ModelMultipleChoiceField(queryset=Address.objects, required=False, addresses = forms.ModelMultipleChoiceField(queryset=Address.objects, required=False,
widget=widgets.FilteredSelectMultiple(verbose_name=_('Pizzas'), is_stacked=False)) widget=widgets.FilteredSelectMultiple(verbose_name=_('addresses'), is_stacked=False))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MailboxForm, self).__init__(*args, **kwargs) super(MailboxForm, self).__init__(*args, **kwargs)

View File

@ -8,7 +8,6 @@ from orchestra.core import services
from . import validators, settings from . import validators, settings
# TODO rename app to mailboxes
class Mailbox(models.Model): class Mailbox(models.Model):
CUSTOM = 'CUSTOM' CUSTOM = 'CUSTOM'

View File

@ -1,3 +1,4 @@
import os
import textwrap import textwrap
from django.conf import settings from django.conf import settings
@ -10,6 +11,10 @@ MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL', 'domains.Do
MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME', '/home/%(name)s/') MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME', '/home/%(name)s/')
MAILBOXES_SIEVE_PATH = getattr(settings, 'MAILBOXES_SIEVE_PATH',
os.path.join(MAILBOXES_HOME, 'Maildir/sieve/orchestra.sieve'))
MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH', '/dev/shm') MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH', '/dev/shm')

View File

@ -0,0 +1,21 @@
from django.contrib import messages
from django.db import transaction
from django.shortcuts import redirect
from django.utils.translation import ungettext, ugettext_lazy as _
@transaction.atomic
def run_monitor(modeladmin, request, queryset):
for resource in queryset:
resource.monitor()
modeladmin.log_change(request, resource, _("Run monitors"))
num = len(queryset)
msg = ungettext(
_("One selected resource has been monitored."),
_("%s selected resource have been monitored.") % num,
num)
modeladmin.message_user(request, msg)
referer = request.META.get('HTTP_REFERER')
if referer:
return redirect(referer)
run_monitor.url_name = 'monitor'

View File

@ -1,8 +1,9 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.core.urlresolvers import reverse
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.filters import UsedContentTypeFilter from orchestra.admin.filters import UsedContentTypeFilter
@ -10,6 +11,7 @@ from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_
from orchestra.core import services from orchestra.core import services
from orchestra.utils import database_ready from orchestra.utils import database_ready
from .actions import run_monitor
from .forms import ResourceForm from .forms import ResourceForm
from .models import Resource, ResourceData, MonitorData from .models import Resource, ResourceData, MonitorData
@ -66,27 +68,49 @@ class ResourceAdmin(ExtendedModelAdmin):
return super(ResourceAdmin, self).formfield_for_dbfield(db_field, **kwargs) return super(ResourceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
class ResourceDataAdmin(admin.ModelAdmin): class ResourceDataAdmin(ExtendedModelAdmin):
list_display = ( list_display = (
'id', 'resource_link', 'used', 'allocated', 'updated_at', 'content_object_link' 'id', 'resource_link', 'content_object_link', 'used', 'allocated', 'display_unit',
'display_updated'
) )
list_filter = ('resource',) list_filter = ('resource',)
readonly_fields = ('content_object_link',) add_fields = ('resource', 'content_type', 'object_id', 'used', 'updated_at', 'allocated')
fields = (
'resource_link', 'content_type', 'content_object_link', 'used', 'display_updated',
'allocated', 'display_unit'
)
readonly_fields = ('display_unit',)
change_readonly_fields = (
'resource_link', 'content_type', 'content_object_link', 'used', 'display_updated',
'display_unit'
)
actions = (run_monitor,)
change_view_actions = actions
ordering = ('-updated_at',)
resource_link = admin_link('resource') resource_link = admin_link('resource')
content_object_link = admin_link('content_object') content_object_link = admin_link('content_object')
display_updated = admin_date('updated_at', short_description=_("Updated"))
def display_unit(self, data):
return data.unit
display_unit.short_description = _("Unit")
display_unit.admin_order_field = 'resource__unit'
def get_queryset(self, request): def get_queryset(self, request):
queryset = super(ResourceDataAdmin, self).get_queryset(request) queryset = super(ResourceDataAdmin, self).get_queryset(request)
return queryset.prefetch_related('content_object') return queryset.prefetch_related('content_object')
class MonitorDataAdmin(admin.ModelAdmin): class MonitorDataAdmin(ExtendedModelAdmin):
list_display = ('id', 'monitor', 'created_at', 'value', 'content_object_link') list_display = ('id', 'monitor', 'display_created', 'value', 'content_object_link')
list_filter = ('monitor',) list_filter = ('monitor',)
readonly_fields = ('content_object_link',) add_fields = ('monitor', 'content_type', 'object_id', 'created_at', 'value')
fields = ('monitor', 'content_type', 'content_object_link', 'display_created', 'value')
change_readonly_fields = fields
content_object_link = admin_link('content_object') content_object_link = admin_link('content_object')
display_created = admin_date('created_at', short_description=_("Created"))
def get_queryset(self, request): def get_queryset(self, request):
queryset = super(MonitorDataAdmin, self).get_queryset(request) queryset = super(MonitorDataAdmin, self).get_queryset(request)
@ -109,10 +133,21 @@ def resource_inline_factory(resources):
def forms(self, resources=resources): def forms(self, resources=resources):
forms = [] forms = []
resources_copy = list(resources) resources_copy = list(resources)
for i, data in enumerate(self.queryset): queryset = self.queryset
if self.instance.pk:
# Create missing resource data
queryset = list(queryset)
queryset_resources = [data.resource for data in queryset]
for resource in resources:
if resource not in queryset_resources:
data = resource.dataset.create(content_object=self.instance)
queryset.append(data)
# Existing dataset
for i, data in enumerate(queryset):
forms.append(self._construct_form(i, resource=data.resource)) forms.append(self._construct_form(i, resource=data.resource))
resources_copy.remove(data.resource) resources_copy.remove(data.resource)
for i, resource in enumerate(resources_copy, len(self.queryset)): # Missing dataset
for i, resource in enumerate(resources_copy, len(queryset)):
forms.append(self._construct_form(i, resource=resource)) forms.append(self._construct_form(i, resource=resource))
return forms return forms
@ -123,9 +158,9 @@ def resource_inline_factory(resources):
formset = ResourceInlineFormSet formset = ResourceInlineFormSet
can_delete = False can_delete = False
fields = ( fields = (
'verbose_name', 'used', 'display_updated', 'allocated', 'unit' 'verbose_name', 'display_used', 'display_updated', 'allocated', 'unit',
) )
readonly_fields = ('used', 'display_updated') readonly_fields = ('display_used', 'display_updated')
class Media: class Media:
css = { css = {
@ -134,9 +169,20 @@ def resource_inline_factory(resources):
display_updated = admin_date('updated_at', default=_("Never")) display_updated = admin_date('updated_at', default=_("Never"))
def display_used(self, data):
update_link = ''
if data.pk:
url = reverse('admin:resources_resourcedata_monitor', args=(data.pk,))
update_link = '<a href="%s"><strong>%s</strong></a>' % (url, ugettext("Update"))
if data.used is not None:
return '%s %s %s' % (data.used, data.resource.unit, update_link)
return _("Unknonw %s") % update_link
display_used.short_description = _("Used")
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
""" Hidde add another """ """ Hidde add another """
return False return False
return ResourceInline return ResourceInline

View File

@ -50,7 +50,7 @@ class ServiceMonitor(ServiceBackend):
data = self.get_last_data(object_id) data = self.get_last_data(object_id)
if data is None: if data is None:
return self.current_date - datetime.timedelta(days=1) return self.current_date - datetime.timedelta(days=1)
return data.date return data.created_at
def process(self, line): def process(self, line):
""" line -> object_id, value """ """ line -> object_id, value """
@ -66,7 +66,7 @@ class ServiceMonitor(ServiceBackend):
line = line.strip() line = line.strip()
object_id, value = self.process(line) object_id, value = self.process(line)
MonitorData.objects.create(monitor=name, object_id=object_id, MonitorData.objects.create(monitor=name, object_id=object_id,
content_type=ct, value=value, date=self.current_date) content_type=ct, value=value, created_at=self.current_date)
def execute(self, server): def execute(self, server):
log = super(ServiceMonitor, self).execute(server) log = super(ServiceMonitor, self).execute(server)

View File

@ -26,11 +26,11 @@ class ResourceForm(forms.ModelForm):
self.fields['allocated'].required = True self.fields['allocated'].required = True
self.fields['allocated'].initial = self.resource.default_allocation self.fields['allocated'].initial = self.resource.default_allocation
def has_changed(self): # def has_changed(self):
""" Make sure resourcedata objects are created for all resources """ # """ Make sure resourcedata objects are created for all resources """
if not self.instance.pk: # if not self.instance.pk:
return True # return True
return super(ResourceForm, self).has_changed() # return super(ResourceForm, self).has_changed()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.instance.resource_id = self.resource.pk self.instance.resource_id = self.resource.pk

View File

@ -61,4 +61,4 @@ def compute_resource_usage(data):
has_result = True has_result = True
else: else:
raise NotImplementedError("%s support not implemented" % data.period) raise NotImplementedError("%s support not implemented" % data.period)
return result/resource.get_scale() if has_result else None return float(result)/resource.get_scale() if has_result else None

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from djcelery.models import PeriodicTask, CrontabSchedule from djcelery.models import PeriodicTask, CrontabSchedule
@ -11,7 +12,7 @@ from orchestra.models import queryset, fields
from orchestra.utils.paths import get_project_root from orchestra.utils.paths import get_project_root
from orchestra.utils.system import run from orchestra.utils.system import run
from . import helpers from . import helpers, tasks
from .backends import ServiceMonitor from .backends import ServiceMonitor
from .validators import validate_scale from .validators import validate_scale
@ -127,7 +128,7 @@ class ResourceData(models.Model):
resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource")) resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource"))
content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
object_id = models.PositiveIntegerField(_("object id")) object_id = models.PositiveIntegerField(_("object id"))
used = models.PositiveIntegerField(_("used"), null=True) used = models.DecimalField(_("used"), max_digits=16, decimal_places=2, null=True)
updated_at = models.DateTimeField(_("updated"), null=True) updated_at = models.DateTimeField(_("updated"), null=True)
allocated = models.PositiveIntegerField(_("allocated"), null=True, blank=True) allocated = models.PositiveIntegerField(_("allocated"), null=True, blank=True)
@ -159,6 +160,9 @@ class ResourceData(models.Model):
self.used = current or 0 self.used = current or 0
self.updated_at = timezone.now() self.updated_at = timezone.now()
self.save(update_fields=['used', 'updated_at']) self.save(update_fields=['used', 'updated_at'])
def monitor(self):
tasks.monitor(self.resource_id, ids=(self.object_id,))
class MonitorData(models.Model): class MonitorData(models.Model):
@ -167,7 +171,7 @@ class MonitorData(models.Model):
choices=ServiceMonitor.get_plugin_choices()) choices=ServiceMonitor.get_plugin_choices())
content_type = models.ForeignKey(ContentType, verbose_name=_("content type")) content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
object_id = models.PositiveIntegerField(_("object id")) object_id = models.PositiveIntegerField(_("object id"))
created_at = models.DateTimeField(_("created"), auto_now_add=True) created_at = models.DateTimeField(_("created"))
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2) value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
content_object = GenericForeignKey() content_object = GenericForeignKey()
@ -178,6 +182,10 @@ class MonitorData(models.Model):
def __unicode__(self): def __unicode__(self):
return str(self.monitor) return str(self.monitor)
@cached_property
def unit(self):
return self.resource.unit
def create_resource_relation(): def create_resource_relation():

View File

@ -2,29 +2,39 @@ from celery import shared_task
from django.db.models.loading import get_model from django.db.models.loading import get_model
from orchestra.apps.orchestration.models import BackendOperation as Operation from orchestra.apps.orchestration.models import BackendOperation as Operation
from orchestra.models.utils import get_model_field_path
from .backends import ServiceMonitor from .backends import ServiceMonitor
from .models import ResourceData, Resource
@shared_task(name='resources.Monitor') @shared_task(name='resources.Monitor')
def monitor(resource_id): def monitor(resource_id, ids=None):
resource = Resource.objects.get(pk=resource_id) from .models import ResourceData, Resource
resource = Resource.objects.get(pk=resource_id)
resource_model = resource.content_type.model_class()
# Execute monitors # Execute monitors
for monitor_name in resource.monitors: for monitor_name in resource.monitors:
backend = ServiceMonitor.get_backend(monitor_name) backend = ServiceMonitor.get_backend(monitor_name)
model = get_model(backend.model) model = get_model(backend.model)
kwargs = {}
if ids:
path = get_model_field_path(model, resource_model)
path = '%s__in' % ('__'.join(path) or 'id')
kwargs = {
path: ids
}
operations = [] operations = []
# Execute monitor # Execute monitor
for obj in model.objects.all(): for obj in model.objects.filter(**kwargs):
operations.append(Operation.create(backend, obj, Operation.MONITOR)) operations.append(Operation.create(backend, obj, Operation.MONITOR))
Operation.execute(operations) Operation.execute(operations)
kwargs = {'id__in': ids} if ids else {}
# Update used resources and trigger resource exceeded and revovery # Update used resources and trigger resource exceeded and revovery
operations = [] operations = []
model = resource.content_type.model_class() model = resource.content_type.model_class()
for obj in model.objects.all(): for obj in model.objects.filter(**kwargs):
data = ResourceData.get_or_create(obj, resource) data = ResourceData.get_or_create(obj, resource)
data.update() data.update()
if not resource.disable_trigger: if not resource.disable_trigger:

View File

@ -66,15 +66,14 @@ view_help.verbose_name = _("Help")
def clone(modeladmin, request, queryset): def clone(modeladmin, request, queryset):
service = queryset.get() service = queryset.get()
fields = ( fields = modeladmin.get_fields(request)
'content_type_id', 'match', 'handler_type', 'is_active', 'ignore_superusers', 'billing_period', fk_fields = ('content_type',)
'billing_point', 'is_fee', 'metric', 'nominal_price', 'tax', 'pricing_period',
'rate_algorithm', 'on_cancel', 'payment_style',
)
query = [] query = []
for field in fields: for field in fields:
value = getattr(service, field) if field in fk_fields:
field = field.replace('_id', '') value = getattr(service, field + '_id')
else:
value = getattr(service, field)
query.append('%s=%s' % (field, value)) query.append('%s=%s' % (field, value))
opts = service._meta opts = service._meta
url = reverse('admin:%s_%s_add' % (opts.app_label, opts.model_name)) url = reverse('admin:%s_%s_add' % (opts.app_label, opts.model_name))

View File

@ -186,7 +186,7 @@ class Service(models.Model):
nominal_price = models.DecimalField(_("nominal price"), max_digits=12, nominal_price = models.DecimalField(_("nominal price"), max_digits=12,
decimal_places=2) decimal_places=2)
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES, tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
default=settings.SERVICES_SERVICE_DEFAUL_TAX) default=settings.SERVICES_SERVICE_DEFAULT_TAX)
pricing_period = models.CharField(_("pricing period"), max_length=16, pricing_period = models.CharField(_("pricing period"), max_length=16,
help_text=_("Time period that is used for computing the rate metric."), help_text=_("Time period that is used for computing the rate metric."),
choices=( choices=(

View File

@ -9,7 +9,7 @@ SERVICES_SERVICE_TAXES = getattr(settings, 'SERVICES_SERVICE_TAXES', (
(21, "21%"), (21, "21%"),
)) ))
SERVICES_SERVICE_DEFAUL_TAX = getattr(settings, 'ORDERS_SERVICE_DFAULT_TAX', 0) SERVICES_SERVICE_DEFAULT_TAX = getattr(settings, 'SERVICES_SERVICE_DEFAULT_TAX', 0)
SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 1) SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 1)

View File

@ -68,7 +68,7 @@ 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 | xargs echo %(object_id)s" % context) self.append("du -s %(home)s | cut -f1 | xargs echo %(object_id)s" % context)
def get_context(self, user): def get_context(self, user):
context = SystemUserBackend().get_context(user) context = SystemUserBackend().get_context(user)

View File

@ -13,7 +13,8 @@ from . import settings
class WebApp(models.Model): class WebApp(models.Model):
""" Represents a web application """ """ Represents a web application """
name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name],
blank=settings.WEBAPPS_ALLOW_BLANK_NAME)
type = models.CharField(_("type"), max_length=32, type = models.CharField(_("type"), max_length=32,
choices=dict_setting_to_choices(settings.WEBAPPS_TYPES), choices=dict_setting_to_choices(settings.WEBAPPS_TYPES),
default=settings.WEBAPPS_DEFAULT_TYPE) default=settings.WEBAPPS_DEFAULT_TYPE)
@ -26,7 +27,7 @@ class WebApp(models.Model):
verbose_name_plural = _("Web Apps") verbose_name_plural = _("Web Apps")
def __unicode__(self): def __unicode__(self):
return self.name return self.name or settings.WEBAPPS_BLANK_NAME
@cached @cached
def get_options(self): def get_options(self):

View File

@ -10,6 +10,12 @@ WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
'127.0.0.1:%(fpm_port)s') '127.0.0.1:%(fpm_port)s')
WEBAPPS_ALLOW_BLANK_NAME = getattr(settings, 'WEBAPPS_ALLOW_BLANK_NAME', False)
# Default name when blank
WEBAPPS_BLANK_NAME = getattr(settings, 'WEBAPPS_BLANK_NAME', 'webapp')
WEBAPPS_FPM_START_PORT = getattr(settings, 'WEBAPPS_FPM_START_PORT', 10000) WEBAPPS_FPM_START_PORT = getattr(settings, 'WEBAPPS_FPM_START_PORT', 10000)

View File

@ -3,7 +3,6 @@ import os
import re import re
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
@ -191,17 +190,15 @@ class Apache2Traffic(ServiceMonitor):
verbose_name = _("Apache 2 Traffic") verbose_name = _("Apache 2 Traffic")
def prepare(self): def prepare(self):
current_date = timezone.localtime(self.current_date) current_date = self.current_date.strftime("%Y-%m-%d %H:%M:%S %Z")
current_date = current_date.strftime("%Y%m%d%H%M%S")
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
function monitor () { function monitor () {
OBJECT_ID=$1 OBJECT_ID=$1
INI_DATE=$2 INI_DATE=$2
LOG_FILE="$3" LOG_FILE="$3"
{ {
awk -v ini="${INI_DATE}" ' awk -v ini="${INI_DATE}" -v end="$(date '+%%Y%%m%%d%%H%%M%%S' -d '%s')" '
BEGIN { BEGIN {
end = "%s"
sum = 0 sum = 0
months["Jan"] = "01"; months["Jan"] = "01";
months["Feb"] = "02"; months["Feb"] = "02";
@ -235,12 +232,12 @@ class Apache2Traffic(ServiceMonitor):
def monitor(self, site): def monitor(self, site):
context = self.get_context(site) context = self.get_context(site)
self.append('monitor %(object_id)i %(last_date)s "%(log_file)s"' % context) self.append('monitor {object_id} $(date "+%Y%m%d%H%M%S" -d "{last_date}") "{log_file}"'.format(**context))
def get_context(self, site): def get_context(self, site):
last_date = timezone.localtime(self.get_last_date(site.pk)) last_date = self.get_last_date(site.pk)
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': last_date.strftime("%Y%m%d%H%M%S"), 'last_date': last_date.strftime("%Y-%m-%d %H:%M:%S %Z"),
'object_id': site.pk, 'object_id': site.pk,
} }

View File

@ -133,7 +133,7 @@ function install_requirements () {
xvfb \ xvfb \
ca-certificates" ca-certificates"
PIP="django==1.7 \ PIP="django==1.7.1 \
django-celery-email==1.0.4 \ django-celery-email==1.0.4 \
django-fluent-dashboard==0.3.5 \ django-fluent-dashboard==0.3.5 \
https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip \ https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip \