From 3e246f9fe0c1d57640aea14b151fc1eceb12be02 Mon Sep 17 00:00:00 2001 From: Marc Date: Wed, 15 Oct 2014 12:47:28 +0000 Subject: [PATCH] lists functional tests passing --- TODO.md | 4 + orchestra/api/actions.py | 15 +- orchestra/apps/databases/backends.py | 1 + .../databases/tests/functional_tests/tests.py | 3 + orchestra/apps/lists/admin.py | 6 +- orchestra/apps/lists/backends.py | 93 ++++++--- orchestra/apps/lists/models.py | 2 + orchestra/apps/lists/settings.py | 7 +- .../lists/tests/functional_tests/tests.py | 188 +++++++++++++++--- .../mails/tests/functional_tests/tests.py | 3 + orchestra/apps/orchestration/methods.py | 8 +- orchestra/apps/orchestration/middlewares.py | 10 +- orchestra/apps/orchestration/models.py | 4 + .../tests/functional_tests/tests.py | 14 +- .../websites/tests/functional_tests/tests.py | 9 +- orchestra/bin/orchestra-admin | 3 +- scripts/services/mailman.md | 6 + 17 files changed, 290 insertions(+), 86 deletions(-) diff --git a/TODO.md b/TODO.md index ab03b9ad..9c8b7a93 100644 --- a/TODO.md +++ b/TODO.md @@ -147,3 +147,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im * POST only fields (account, username, name) etc + +* for list virtual_domains cleaning up we need to know the old domain name when a list changes its address domain, but this is not possible with the current design. + +* update_fields=[] doesn't trigger post save! diff --git a/orchestra/api/actions.py b/orchestra/api/actions.py index bfc3f04b..257f138c 100644 --- a/orchestra/api/actions.py +++ b/orchestra/api/actions.py @@ -11,11 +11,20 @@ class SetPasswordApiMixin(object): obj = self.get_object() data = request.DATA if isinstance(data, basestring): - data = {'password': data} + data = { + 'password': data + } serializer = SetPasswordSerializer(data=data) if serializer.is_valid(): obj.set_password(serializer.data['password']) - obj.save(update_fields=['password']) - return Response({'status': 'password changed'}) + try: + obj.save(update_fields=['password']) + except ValueError: + # Some services don't store the password on the database + # update_fields=[] doesn't trigger post save! + obj.save() + return Response({ + 'status': 'password changed' + }) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/orchestra/apps/databases/backends.py b/orchestra/apps/databases/backends.py index 486c9887..3b0c611f 100644 --- a/orchestra/apps/databases/backends.py +++ b/orchestra/apps/databases/backends.py @@ -74,6 +74,7 @@ class MySQLUserBackend(ServiceController): } +# TODO https://docs.djangoproject.com/en/1.7/ref/signals/#m2m-changed class MySQLPermissionBackend(ServiceController): model = 'databases.UserDatabaseRelation' verbose_name = "MySQL permission" diff --git a/orchestra/apps/databases/tests/functional_tests/tests.py b/orchestra/apps/databases/tests/functional_tests/tests.py index e5b2d68d..765f01e8 100644 --- a/orchestra/apps/databases/tests/functional_tests/tests.py +++ b/orchestra/apps/databases/tests/functional_tests/tests.py @@ -70,6 +70,9 @@ class DatabaseTestMixin(object): self.validate_login_error(dbname, username, password) self.validate_create_table(dbname, username, new_password) + # TODO test add user + # TODO remove user + # TODO remove all users class MySQLBackendMixin(object): db_type = 'mysql' diff --git a/orchestra/apps/lists/admin.py b/orchestra/apps/lists/admin.py index 11ed445e..89b3e2eb 100644 --- a/orchestra/apps/lists/admin.py +++ b/orchestra/apps/lists/admin.py @@ -3,7 +3,7 @@ from django.conf.urls import patterns from django.contrib.auth.admin import UserAdmin from django.utils.translation import ugettext, ugettext_lazy as _ -from orchestra.admin import ExtendedModelAdmin +from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin.utils import admin_link from orchestra.apps.accounts.admin import SelectAccountAdminMixin @@ -11,7 +11,7 @@ from .forms import ListCreationForm, ListChangeForm from .models import List -class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): +class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ('name', 'address_name', 'address_domain_link', 'account_link') add_fieldsets = ( (None, { @@ -38,7 +38,7 @@ class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): }), (_("Admin"), { 'classes': ('wide',), - 'fields': ('admin_email', 'password',), + 'fields': ('password',), }), ) readonly_fields = ('account_link',) diff --git a/orchestra/apps/lists/backends.py b/orchestra/apps/lists/backends.py index 055288e8..509a9e84 100644 --- a/orchestra/apps/lists/backends.py +++ b/orchestra/apps/lists/backends.py @@ -1,3 +1,4 @@ +import re import textwrap from django.utils import timezone @@ -12,8 +13,23 @@ from .models import List class MailmanBackend(ServiceController): verbose_name = "Mailman" model = 'lists.List' + addresses = [ + '', + '-admin', + '-bounces', + '-confirm', + '-join', + '-leave', + '-owner', + '-request', + '-subscribe', + '-unsubscribe' + ] def include_virtual_alias_domain(self, context): + # TODO for list virtual_domains cleaning up we need to know the old domain name when a list changes its address + # domain, but this is not possible with the current design. + # sync the whole file everytime? if context['address_domain']: self.append(textwrap.dedent(""" [[ $(grep "^\s*%(address_domain)s\s*$" %(virtual_alias_domains)s) ]] || { @@ -29,42 +45,62 @@ class MailmanBackend(ServiceController): def get_virtual_aliases(self, context): aliases = [] - addresses = [ - '', - '-admin', - '-bounces', - '-confirm', - '-join', - '-leave', - '-owner', - '-request', - '-subscribe', - '-unsubscribe' - ] - for address in addresses: + for address in self.addresses: context['address'] = address aliases.append("%(address_name)s%(address)s@%(domain)s\t%(name)s%(address)s" % context) return '\n'.join(aliases) def save(self, mail_list): - if not getattr(mail_list, 'password', None): - # TODO - # Create only support for now - return context = self.get_context(mail_list) - self.append("newlist --quiet --emailhost='%(domain)s' '%(name)s' '%(admin)s' '%(password)s'" % context) + # Create list + self.append(textwrap.dedent("""\ + [[ ! -e %(mailman_root)s/lists/%(name)s ]] && { + newlist --quiet --emailhost='%(domain)s' '%(name)s' '%(admin)s' '%(password)s' + }""" % context)) + # Custom domain if mail_list.address: - context['aliases'] = self.get_virtual_aliases(context) - self.append( - "if [[ ! $(grep '^\s*%(name)s\s' %(virtual_alias)s) ]]; then\n" - " echo '# %(banner)s\n%(aliases)s\n' >> %(virtual_alias)s\n" - " UPDATED_VIRTUAL_ALIAS=1\n" - "fi" % context - ) + aliases = self.get_virtual_aliases(context) + # Preserve indentation + spaces = ' '*4 + context['aliases'] = spaces + aliases.replace('\n', '\n'+spaces) + self.append(textwrap.dedent("""\ + if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then + echo '# %(banner)s\n%(aliases)s + ' >> %(virtual_alias)s + UPDATED_VIRTUAL_ALIAS=1 + else + if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then + sed -i "s/^.*\s%(name)s\s*$//" %(virtual_alias)s + echo '# %(banner)s\n%(aliases)s + ' >> %(virtual_alias)s + UPDATED_VIRTUAL_ALIAS=1 + fi + fi""" % context + )) + self.append('echo "require_explicit_destination = 0" | ' + '%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s' % context) + self.append(textwrap.dedent("""\ + echo "host_name = '%(address_domain)s'" | \ + %(mailman_root)s/bin/config_list -i /dev/stdin %(name)s""" % context)) + else: + # Cleanup shit + self.append(textwrap.dedent("""\ + if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then + sed -i "s/^.*\s%(name)s\s*$//" %(virtual_alias)s + fi""" % context + )) + # Update + if context['password'] is not None: + self.append('%(mailman_root)s/bin/change_pw --listname="%(name)s" --password="%(password)s"' % context) self.include_virtual_alias_domain(context) def delete(self, mail_list): - pass + context = self.get_context(mail_list) + self.exclude_virtual_alias_domain(context) + for address in self.addresses: + context['address'] = address + self.append('sed -i "s/^.*\s%(name)s%(address)s\s*$//" %(virtual_alias)s' % context) + self.append("rmlist -a %(name)s" % context) def commit(self): context = self.get_context_files() @@ -77,7 +113,7 @@ class MailmanBackend(ServiceController): def get_context_files(self): return { 'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH, - 'virtual_alias_domains': settings.MAILS_VIRTUAL_ALIAS_DOMAINS_PATH, + 'virtual_alias_domains': settings.LISTS_VIRTUAL_ALIAS_DOMAINS_PATH, } def get_context(self, mail_list): @@ -90,6 +126,7 @@ class MailmanBackend(ServiceController): 'address_name': mail_list.address_name, 'address_domain': mail_list.address_domain, 'admin': mail_list.admin_email, + 'mailman_root': settings.LISTS_MAILMAN_ROOT_PATH, }) return context @@ -101,7 +138,7 @@ class MailmanTraffic(ServiceMonitor): def prepare(self): current_date = timezone.localtime(self.current_date) current_date = current_date.strftime("%b %d %H:%M:%S") - self.append(textwrap.dedent(""" + self.append(textwrap.dedent("""\ function monitor () { OBJECT_ID=$1 LAST_DATE=$2 diff --git a/orchestra/apps/lists/models.py b/orchestra/apps/lists/models.py index d4b9c75c..7956bcab 100644 --- a/orchestra/apps/lists/models.py +++ b/orchestra/apps/lists/models.py @@ -21,6 +21,8 @@ class List(models.Model): account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='lists') + password = None + class Meta: unique_together = ('address_name', 'address_domain') diff --git a/orchestra/apps/lists/settings.py b/orchestra/apps/lists/settings.py index 6b2de33f..a34c0180 100644 --- a/orchestra/apps/lists/settings.py +++ b/orchestra/apps/lists/settings.py @@ -1,7 +1,6 @@ from django.conf import settings - LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain') @@ -12,9 +11,13 @@ LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH', '/var/log/mailman/post') +LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH', + '/var/lib/mailman/') + + LISTS_VIRTUAL_ALIAS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_PATH', '/etc/postfix/mailman_virtual_aliases') -MAILS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILS_VIRTUAL_ALIAS_DOMAINS_PATH', +LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_DOMAINS_PATH', '/etc/postfix/mailman_virtual_domains') diff --git a/orchestra/apps/lists/tests/functional_tests/tests.py b/orchestra/apps/lists/tests/functional_tests/tests.py index 496f0262..f64c4b63 100644 --- a/orchestra/apps/lists/tests/functional_tests/tests.py +++ b/orchestra/apps/lists/tests/functional_tests/tests.py @@ -3,6 +3,7 @@ import os import smtplib import time import textwrap +import requests from email.mime.text import MIMEText from django.conf import settings as djsettings @@ -11,12 +12,14 @@ from django.core.management.base import CommandError from django.core.urlresolvers import reverse from selenium.webdriver.support.select import Select +from orchestra.admin.utils import change_url from orchestra.apps.accounts.models import Account from orchestra.apps.domains.models import Domain from orchestra.apps.orchestration.models import Server, Route from orchestra.apps.resources.models import Resource from orchestra.utils.system import run, sshrun -from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii, snapshot_on_error, save_response_on_error +from orchestra.utils.tests import (BaseLiveServerTestCase, random_ascii, snapshot_on_error, + save_response_on_error) from ... import backends, settings from ...models import List @@ -40,10 +43,30 @@ class ListMixin(object): if not address: address = "%s@%s" % (name, settings.LISTS_DEFAULT_DOMAIN) subscribe_address = "{}-subscribe@{}".format(*address.split('@')) + request_address = "{}-request@{}".format(name, address.split('@')[1]) self.subscribe(subscribe_address) - time.sleep(2) + time.sleep(3) sshrun(self.MASTER_SERVER, - 'grep -v ":\|^\s\|^$\|-\|\.\|\s" /var/spool/mail/nobody | base64 -d | grep "%s"' % address, display=False) + 'grep -v ":\|^\s\|^$\|-\|\.\|\s" /var/spool/mail/nobody | base64 -d | grep "%s"' + % request_address, display=False) + + def validate_login(self, name, password): + url = 'http://%s/cgi-bin/mailman/admin/%s' % (settings.LISTS_DEFAULT_DOMAIN, name) + self.assertEqual(200, requests.post(url, data={'adminpw': password}).status_code) + + def validate_delete(self, name): + context = { + 'name': name, + 'domain': Domain.objects.get().name, + 'virtual_domain': settings.LISTS_VIRTUAL_ALIAS_DOMAINS_PATH, + 'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH, + } + self.assertRaises(CommandError, sshrun, self.MASTER_SERVER, + 'grep "\s%(name)s\s*" %(virtual_alias)s' % context, display=False) + self.assertRaises(CommandError, sshrun, self.MASTER_SERVER, + 'grep "^\s*$(domain)s\s*$" %(virtual_domain)s' % context, display=False) + self.assertRaises(CommandError, sshrun, self.MASTER_SERVER, + 'list_lists | grep -i "^\s*%(name)s\s"' % context, display=False) def subscribe(self, subscribe_address): msg = MIMEText('') @@ -70,18 +93,74 @@ class ListMixin(object): admin_email = 'root@test3.orchestra.lan' self.add(name, password, admin_email) self.validate_add(name) -# self.addCleanup(self.delete, username) + self.validate_login(name, password) + self.addCleanup(self.delete, name) def test_add_with_address(self): name = '%s_list' % random_ascii(10) password = '@!?%spppP001' % random_ascii(5) - print password admin_email = 'root@test3.orchestra.lan' address_name = '%s_name' % random_ascii(10) domain_name = '%sdomain.lan' % random_ascii(10) address_domain = Domain.objects.create(name=domain_name, account=self.account) self.add(name, password, admin_email, address_name=address_name, address_domain=address_domain) + self.addCleanup(self.delete, name) + # Mailman doesn't support changing the address, only the domain self.validate_add(name, address="%s@%s" % (address_name, address_domain)) + + def test_change_password(self): + name = '%s_list' % random_ascii(10) + password = '@!?%spppP001' % random_ascii(5) + admin_email = 'root@test3.orchestra.lan' + self.add(name, password, admin_email) + self.addCleanup(self.delete, name) + self.validate_login(name, password) + new_password = '@!?%spppP001' % random_ascii(5) + self.change_password(name, new_password) + self.validate_login(name, new_password) + + def test_change_domain(self): + name = '%s_list' % random_ascii(10) + password = '@!?%spppP001' % random_ascii(5) + admin_email = 'root@test3.orchestra.lan' + address_name = '%s_name' % random_ascii(10) + domain_name = '%sdomain.lan' % random_ascii(10) + address_domain = Domain.objects.create(name=domain_name, account=self.account) + self.add(name, password, admin_email, address_name=address_name, address_domain=address_domain) + self.addCleanup(self.delete, name) + # Mailman doesn't support changing the address, only the domain + domain_name = '%sdomain.lan' % random_ascii(10) + address_domain = Domain.objects.create(name=domain_name, account=self.account) + self.update_domain(name, domain_name) + self.validate_add(name, address="%s@%s" % (address_name, address_domain)) + + def test_change_address_name(self): + name = '%s_list' % random_ascii(10) + password = '@!?%spppP001' % random_ascii(5) + admin_email = 'root@test3.orchestra.lan' + address_name = '%s_name' % random_ascii(10) + domain_name = '%sdomain.lan' % random_ascii(10) + address_domain = Domain.objects.create(name=domain_name, account=self.account) + self.add(name, password, admin_email, address_name=address_name, address_domain=address_domain) +# self.addCleanup(self.delete, name) + # Mailman doesn't support changing the address, only the domain + address_name = '%s_name' % random_ascii(10) + self.update_address_name(name, address_name) + self.validate_add(name, address="%s@%s" % (address_name, address_domain)) + + def test_delete(self): + name = '%s_list' % random_ascii(10) + password = '@!?%spppP001' % random_ascii(5) + admin_email = 'root@test3.orchestra.lan' + address_name = '%s_name' % random_ascii(10) + domain_name = '%sdomain.lan' % random_ascii(10) + address_domain = Domain.objects.create(name=domain_name, account=self.account) + self.add(name, password, admin_email, address_name=address_name, address_domain=address_domain) + # Mailman doesn't support changing the address, only the domain + self.validate_add(name, address="%s@%s" % (address_name, address_domain)) + self.delete(name) + self.assertRaises(AssertionError, self.validate_login, name, password) + self.validate_delete(name) class RESTListMixin(ListMixin): @@ -101,8 +180,23 @@ class RESTListMixin(ListMixin): @save_response_on_error def delete(self, name): - list = self.rest.lists.retrieve(name=name).get() - list.delete() + self.rest.lists.retrieve(name=name).delete() + + @save_response_on_error + def change_password(self, name, password): + mail_list = self.rest.lists.retrieve(name=name).get() + mail_list.set_password(password) + + @save_response_on_error + def update_domain(self, name, domain_name): + mail_list = self.rest.lists.retrieve(name=name).get() + domain = self.rest.domains.retrieve(name=domain_name).get() + mail_list.update(address_domain=domain.url) + + @save_response_on_error + def update_address_name(self, name, address_name): + mail_list = self.rest.lists.retrieve(name=name).get() + mail_list.update(address_name=address_name) class AdminListMixin(ListMixin): @@ -111,48 +205,76 @@ class AdminListMixin(ListMixin): self.admin_login() @snapshot_on_error - def add(self, name, password, admin_email): - url = self.live_server_url + reverse('admin:mails_List_add') + def add(self, name, password, admin_email, address_name=None, address_domain=None): + url = self.live_server_url + reverse('admin:lists_list_add') self.selenium.get(url) - account_input = self.selenium.find_element_by_id('id_account') - account_select = Select(account_input) - account_select.select_by_value(str(self.account.pk)) - name_field = self.selenium.find_element_by_id('id_name') - name_field.send_keys(username) + name_field.send_keys(name) password_field = self.selenium.find_element_by_id('id_password1') password_field.send_keys(password) password_field = self.selenium.find_element_by_id('id_password2') password_field.send_keys(password) - if quota is not None: - quota_id = 'id_resources-resourcedata-content_type-object_id-0-allocated' - quota_field = self.selenium.find_element_by_id(quota_id) - quota_field.clear() - quota_field.send_keys(quota) + admin_email_field = self.selenium.find_element_by_id('id_admin_email') + admin_email_field.send_keys(admin_email) - if filtering is not None: - filtering_input = self.selenium.find_element_by_id('id_filtering') - filtering_select = Select(filtering_input) - filtering_select.select_by_value("CUSTOM") - filtering_inline = self.selenium.find_element_by_id('fieldsetcollapser0') - filtering_inline.click() - time.sleep(0.5) - filtering_field = self.selenium.find_element_by_id('id_custom_filtering') - filtering_field.send_keys(filtering) + if address_name: + address_name_field = self.selenium.find_element_by_id('id_address_name') + address_name_field.send_keys(address_name) + + domain = Domain.objects.get(name=address_domain) + domain_input = self.selenium.find_element_by_id('id_address_domain') + domain_select = Select(domain_input) + domain_select.select_by_value(str(domain.pk)) name_field.submit() self.assertNotEqual(url, self.selenium.current_url) + + @snapshot_on_error + def delete(self, name): + mail_list = List.objects.get(name=name) + self.admin_delete(mail_list) + + @snapshot_on_error + def change_password(self, name, password): + mail_list = List.objects.get(name=name) + self.admin_change_password(mail_list, password) + + @snapshot_on_error + def update_domain(self, name, domain_name): + mail_list = List.objects.get(name=name) + url = self.live_server_url + change_url(mail_list) + self.selenium.get(url) + + domain = Domain.objects.get(name=domain_name) + domain_input = self.selenium.find_element_by_id('id_address_domain') + domain_select = Select(domain_input) + domain_select.select_by_value(str(domain.pk)) + + save = self.selenium.find_element_by_name('_save') + save.submit() + self.assertNotEqual(url, self.selenium.current_url) + + @snapshot_on_error + def update_address_name(self, name, address_name): + mail_list = List.objects.get(name=name) + url = self.live_server_url + change_url(mail_list) + self.selenium.get(url) + + address_name_field = self.selenium.find_element_by_id('id_address_name') + address_name_field.clear() + address_name_field.send_keys(address_name) + + save = self.selenium.find_element_by_name('_save') + save.submit() + self.assertNotEqual(url, self.selenium.current_url) class RESTListTest(RESTListMixin, BaseLiveServerTestCase): pass -#class AdminListTest(AdminListMixin, BaseLiveServerTestCase): -# pass - - - +class AdminListTest(AdminListMixin, BaseLiveServerTestCase): + pass diff --git a/orchestra/apps/mails/tests/functional_tests/tests.py b/orchestra/apps/mails/tests/functional_tests/tests.py index 0b415b83..b3718a31 100644 --- a/orchestra/apps/mails/tests/functional_tests/tests.py +++ b/orchestra/apps/mails/tests/functional_tests/tests.py @@ -233,6 +233,9 @@ class MailboxMixin(object): sshrun(self.MASTER_SERVER, "grep '%s' %s/Maildir/.%s/new/*" % (token, home, folder), display=False) +# TODO test update shit +# TODO test autoreply + class RESTMailboxMixin(MailboxMixin): def setUp(self): diff --git a/orchestra/apps/orchestration/methods.py b/orchestra/apps/orchestration/methods.py index dd64aa99..72a063b4 100644 --- a/orchestra/apps/orchestration/methods.py +++ b/orchestra/apps/orchestration/methods.py @@ -24,6 +24,8 @@ def BashSSH(backend, log, server, cmds): log.script = script log.save(update_fields=['script']) logger.debug('%s is going to be executed on %s' % (backend, server)) + channel = None + ssh = None try: # Avoid "Argument list too long" on large scripts by genereting a file # and scping it to the remote server @@ -93,8 +95,10 @@ def BashSSH(backend, log, server, cmds): logger.debug(log.traceback) log.save() finally: - channel.close() - ssh.close() + if channel is not None: + channel.close() + if ssh is not None: + ssh.close() def Python(backend, log, server, cmds): diff --git a/orchestra/apps/orchestration/middlewares.py b/orchestra/apps/orchestration/middlewares.py index dcc67262..cd31a1f4 100644 --- a/orchestra/apps/orchestration/middlewares.py +++ b/orchestra/apps/orchestration/middlewares.py @@ -1,4 +1,3 @@ -import copy from threading import local from django.core.urlresolvers import resolve @@ -16,12 +15,12 @@ from .models import BackendOperation as Operation @receiver(post_save, dispatch_uid='orchestration.post_save_collector') def post_save_collector(sender, *args, **kwargs): - if sender != BackendLog: + if sender not in [BackendLog, Operation]: OperationsMiddleware.collect(Operation.SAVE, **kwargs) @receiver(pre_delete, dispatch_uid='orchestration.pre_delete_collector') def pre_delete_collector(sender, *args, **kwargs): - if sender != BackendLog: + if sender not in [BackendLog, Operation]: OperationsMiddleware.collect(Operation.DELETE, **kwargs) @@ -49,7 +48,6 @@ class OperationsMiddleware(object): request = getattr(cls.thread_locals, 'request', None) if request is None: return - good_action = action pending_operations = cls.get_pending_operations() for backend in ServiceBackend.get_backends(): instance = None @@ -84,15 +82,13 @@ class OperationsMiddleware(object): break if not execute: continue - instance = copy.copy(instance) - good = instance operation = Operation.create(backend, instance, action) if action != Operation.DELETE: # usually we expect to be using last object state, # except when we are deleting it pending_operations.discard(operation) pending_operations.add(operation) - + def process_request(self, request): """ Store request on a thread local variable """ type(self).thread_locals.request = request diff --git a/orchestra/apps/orchestration/models.py b/orchestra/apps/orchestration/models.py index 826b795f..c04f721f 100644 --- a/orchestra/apps/orchestration/models.py +++ b/orchestra/apps/orchestration/models.py @@ -1,3 +1,4 @@ +import copy import socket from django.contrib.contenttypes import generic @@ -124,6 +125,9 @@ class BackendOperation(models.Model): def create(cls, backend, instance, action): op = cls(backend=backend.get_name(), instance=instance, action=action) op.backend = backend + # instance should maintain any dynamic attribute until backend execution + # deep copy is prefered over copy otherwise objects will share same atributes (queryset cache) + op.instance = copy.deepcopy(instance) return op @classmethod diff --git a/orchestra/apps/systemusers/tests/functional_tests/tests.py b/orchestra/apps/systemusers/tests/functional_tests/tests.py index 369d1481..04ce0c14 100644 --- a/orchestra/apps/systemusers/tests/functional_tests/tests.py +++ b/orchestra/apps/systemusers/tests/functional_tests/tests.py @@ -10,6 +10,7 @@ from django.core.management.base import CommandError from django.core.urlresolvers import reverse from selenium.webdriver.support.select import Select +from orchestra.admin.utils import change_url from orchestra.apps.accounts.models import Account from orchestra.apps.orchestration.models import Server, Route from orchestra.utils.system import run, sshrun @@ -268,8 +269,7 @@ class AdminSystemUserMixin(SystemUserMixin): @snapshot_on_error def add_group(self, username, groupname): user = SystemUser.objects.get(username=username) - change = reverse('admin:systemusers_systemuser_change', args=(user.pk,)) - url = self.live_server_url + change + url = self.live_server_url + change_url(user) self.selenium.get(url) groups = self.selenium.find_element_by_id('id_groups_add_all_link') groups.click() @@ -281,8 +281,7 @@ class AdminSystemUserMixin(SystemUserMixin): @snapshot_on_error def save(self, username): user = SystemUser.objects.get(username=username) - change = reverse('admin:systemusers_systemuser_change', args=(user.pk,)) - url = self.live_server_url + change + url = self.live_server_url + change_url(user) self.selenium.get(url) save = self.selenium.find_element_by_name('_save') save.submit() @@ -367,3 +366,10 @@ class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase): self.assertNotEqual(url, self.selenium.current_url) self.assertRaises(ftplib.error_perm, self.validate_ftp, username, password) + self.selenium.get(url) + self.assertNotEqual(url, self.selenium.current_url) + + # Reenable for test cleanup + self.account.is_active = True + self.account.save() +# self.admin_login() diff --git a/orchestra/apps/websites/tests/functional_tests/tests.py b/orchestra/apps/websites/tests/functional_tests/tests.py index 7d3796e5..b19d1a66 100644 --- a/orchestra/apps/websites/tests/functional_tests/tests.py +++ b/orchestra/apps/websites/tests/functional_tests/tests.py @@ -79,10 +79,7 @@ class RESTWebsiteMixin(RESTWebAppMixin): @save_response_on_error def delete_website(self, name): - print 'hola' - pass self.rest.websites.retrieve(name=name).delete() -# self.rest.websites.retrieve(name=name).delete() @save_response_on_error def add_content(self, website, webapp, path): @@ -94,6 +91,12 @@ class RESTWebsiteMixin(RESTWebAppMixin): }) website.save() + # TODO test disable + # TODO test https (refactor ssl) + # TODO test php options + # TODO read php-version /fpm/fcgid + # TODO max_processes, timeouts, memory... + class StaticRESTWebsiteTest(RESTWebsiteMixin, StaticWebAppMixin, WebsiteMixin, BaseLiveServerTestCase): def test_mix_webapps(self): diff --git a/orchestra/bin/orchestra-admin b/orchestra/bin/orchestra-admin index dc818034..2dc33dd9 100755 --- a/orchestra/bin/orchestra-admin +++ b/orchestra/bin/orchestra-admin @@ -168,7 +168,8 @@ function install_requirements () { orchestra-orm==dev \ django-debug-toolbar==1.2.1 \ django-nose==1.2 \ - sqlparse" + sqlparse + requests" fi # Make sure locales are in place before installing postgres diff --git a/scripts/services/mailman.md b/scripts/services/mailman.md index ef2e467a..9e877e2a 100644 --- a/scripts/services/mailman.md +++ b/scripts/services/mailman.md @@ -46,3 +46,9 @@ sed -i "s/DEFAULT_EMAIL_HOST\s*=\s*.*/DEFAULT_EMAIL_HOST = 'lists.orchestra.lan' sed -i "s/DEFAULT_URL_HOST\s*=\s*.*/DEFAULT_URL_HOST = 'lists.orchestra.lan'/" /etc/mailman/mm_cfg.py +# apache +cp /etc/mailman/apache.conf /etc/apache2/sites-available/mailman.conf +a2ensite mailman.conf +/etc/init.d/apache2 restart + +