Fixes on resources
This commit is contained in:
parent
3b4b69e9a2
commit
d3727f0565
5
TODO.md
5
TODO.md
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|
21
orchestra/apps/resources/actions.py
Normal file
21
orchestra/apps/resources/actions.py
Normal 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'
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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=(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
Loading…
Reference in a new issue