lists functional tests passing

This commit is contained in:
Marc 2014-10-15 12:47:28 +00:00
parent 4c7c5b5505
commit 3e246f9fe0
17 changed files with 290 additions and 86 deletions

View File

@ -147,3 +147,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* POST only fields (account, username, name) etc * 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!

View File

@ -11,11 +11,20 @@ class SetPasswordApiMixin(object):
obj = self.get_object() obj = self.get_object()
data = request.DATA data = request.DATA
if isinstance(data, basestring): if isinstance(data, basestring):
data = {'password': data} data = {
'password': data
}
serializer = SetPasswordSerializer(data=data) serializer = SetPasswordSerializer(data=data)
if serializer.is_valid(): if serializer.is_valid():
obj.set_password(serializer.data['password']) obj.set_password(serializer.data['password'])
obj.save(update_fields=['password']) try:
return Response({'status': 'password changed'}) 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: else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@ -74,6 +74,7 @@ class MySQLUserBackend(ServiceController):
} }
# TODO https://docs.djangoproject.com/en/1.7/ref/signals/#m2m-changed
class MySQLPermissionBackend(ServiceController): class MySQLPermissionBackend(ServiceController):
model = 'databases.UserDatabaseRelation' model = 'databases.UserDatabaseRelation'
verbose_name = "MySQL permission" verbose_name = "MySQL permission"

View File

@ -70,6 +70,9 @@ class DatabaseTestMixin(object):
self.validate_login_error(dbname, username, password) self.validate_login_error(dbname, username, password)
self.validate_create_table(dbname, username, new_password) self.validate_create_table(dbname, username, new_password)
# TODO test add user
# TODO remove user
# TODO remove all users
class MySQLBackendMixin(object): class MySQLBackendMixin(object):
db_type = 'mysql' db_type = 'mysql'

View File

@ -3,7 +3,7 @@ from django.conf.urls import patterns
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext, ugettext_lazy as _ 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.admin.utils import admin_link
from orchestra.apps.accounts.admin import SelectAccountAdminMixin from orchestra.apps.accounts.admin import SelectAccountAdminMixin
@ -11,7 +11,7 @@ from .forms import ListCreationForm, ListChangeForm
from .models import List from .models import List
class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('name', 'address_name', 'address_domain_link', 'account_link') list_display = ('name', 'address_name', 'address_domain_link', 'account_link')
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
@ -38,7 +38,7 @@ class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
}), }),
(_("Admin"), { (_("Admin"), {
'classes': ('wide',), 'classes': ('wide',),
'fields': ('admin_email', 'password',), 'fields': ('password',),
}), }),
) )
readonly_fields = ('account_link',) readonly_fields = ('account_link',)

View File

@ -1,3 +1,4 @@
import re
import textwrap import textwrap
from django.utils import timezone from django.utils import timezone
@ -12,8 +13,23 @@ from .models import List
class MailmanBackend(ServiceController): class MailmanBackend(ServiceController):
verbose_name = "Mailman" verbose_name = "Mailman"
model = 'lists.List' model = 'lists.List'
addresses = [
'',
'-admin',
'-bounces',
'-confirm',
'-join',
'-leave',
'-owner',
'-request',
'-subscribe',
'-unsubscribe'
]
def include_virtual_alias_domain(self, context): 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']: if context['address_domain']:
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
[[ $(grep "^\s*%(address_domain)s\s*$" %(virtual_alias_domains)s) ]] || { [[ $(grep "^\s*%(address_domain)s\s*$" %(virtual_alias_domains)s) ]] || {
@ -29,42 +45,62 @@ class MailmanBackend(ServiceController):
def get_virtual_aliases(self, context): def get_virtual_aliases(self, context):
aliases = [] aliases = []
addresses = [ for address in self.addresses:
'',
'-admin',
'-bounces',
'-confirm',
'-join',
'-leave',
'-owner',
'-request',
'-subscribe',
'-unsubscribe'
]
for address in addresses:
context['address'] = address context['address'] = address
aliases.append("%(address_name)s%(address)s@%(domain)s\t%(name)s%(address)s" % context) aliases.append("%(address_name)s%(address)s@%(domain)s\t%(name)s%(address)s" % context)
return '\n'.join(aliases) return '\n'.join(aliases)
def save(self, mail_list): 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) 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: if mail_list.address:
context['aliases'] = self.get_virtual_aliases(context) aliases = self.get_virtual_aliases(context)
self.append( # Preserve indentation
"if [[ ! $(grep '^\s*%(name)s\s' %(virtual_alias)s) ]]; then\n" spaces = ' '*4
" echo '# %(banner)s\n%(aliases)s\n' >> %(virtual_alias)s\n" context['aliases'] = spaces + aliases.replace('\n', '\n'+spaces)
" UPDATED_VIRTUAL_ALIAS=1\n" self.append(textwrap.dedent("""\
"fi" % context 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) self.include_virtual_alias_domain(context)
def delete(self, mail_list): 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): def commit(self):
context = self.get_context_files() context = self.get_context_files()
@ -77,7 +113,7 @@ class MailmanBackend(ServiceController):
def get_context_files(self): def get_context_files(self):
return { return {
'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH, '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): def get_context(self, mail_list):
@ -90,6 +126,7 @@ class MailmanBackend(ServiceController):
'address_name': mail_list.address_name, 'address_name': mail_list.address_name,
'address_domain': mail_list.address_domain, 'address_domain': mail_list.address_domain,
'admin': mail_list.admin_email, 'admin': mail_list.admin_email,
'mailman_root': settings.LISTS_MAILMAN_ROOT_PATH,
}) })
return context return context
@ -101,7 +138,7 @@ class MailmanTraffic(ServiceMonitor):
def prepare(self): def prepare(self):
current_date = timezone.localtime(self.current_date) current_date = timezone.localtime(self.current_date)
current_date = current_date.strftime("%b %d %H:%M:%S") current_date = current_date.strftime("%b %d %H:%M:%S")
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""\
function monitor () { function monitor () {
OBJECT_ID=$1 OBJECT_ID=$1
LAST_DATE=$2 LAST_DATE=$2

View File

@ -21,6 +21,8 @@ class List(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='lists') related_name='lists')
password = None
class Meta: class Meta:
unique_together = ('address_name', 'address_domain') unique_together = ('address_name', 'address_domain')

View File

@ -1,7 +1,6 @@
from django.conf import settings from django.conf import settings
LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain') LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain')
@ -12,9 +11,13 @@ LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
'/var/log/mailman/post') '/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', LISTS_VIRTUAL_ALIAS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_PATH',
'/etc/postfix/mailman_virtual_aliases') '/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') '/etc/postfix/mailman_virtual_domains')

View File

@ -3,6 +3,7 @@ import os
import smtplib import smtplib
import time import time
import textwrap import textwrap
import requests
from email.mime.text import MIMEText from email.mime.text import MIMEText
from django.conf import settings as djsettings 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 django.core.urlresolvers import reverse
from selenium.webdriver.support.select import Select from selenium.webdriver.support.select import Select
from orchestra.admin.utils import change_url
from orchestra.apps.accounts.models import Account from orchestra.apps.accounts.models import Account
from orchestra.apps.domains.models import Domain from orchestra.apps.domains.models import Domain
from orchestra.apps.orchestration.models import Server, Route from orchestra.apps.orchestration.models import Server, Route
from orchestra.apps.resources.models import Resource from orchestra.apps.resources.models import Resource
from orchestra.utils.system import run, sshrun 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 ... import backends, settings
from ...models import List from ...models import List
@ -40,10 +43,30 @@ class ListMixin(object):
if not address: if not address:
address = "%s@%s" % (name, settings.LISTS_DEFAULT_DOMAIN) address = "%s@%s" % (name, settings.LISTS_DEFAULT_DOMAIN)
subscribe_address = "{}-subscribe@{}".format(*address.split('@')) subscribe_address = "{}-subscribe@{}".format(*address.split('@'))
request_address = "{}-request@{}".format(name, address.split('@')[1])
self.subscribe(subscribe_address) self.subscribe(subscribe_address)
time.sleep(2) time.sleep(3)
sshrun(self.MASTER_SERVER, 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): def subscribe(self, subscribe_address):
msg = MIMEText('') msg = MIMEText('')
@ -70,18 +93,74 @@ class ListMixin(object):
admin_email = 'root@test3.orchestra.lan' admin_email = 'root@test3.orchestra.lan'
self.add(name, password, admin_email) self.add(name, password, admin_email)
self.validate_add(name) 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): def test_add_with_address(self):
name = '%s_list' % random_ascii(10) name = '%s_list' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
print password
admin_email = 'root@test3.orchestra.lan' admin_email = 'root@test3.orchestra.lan'
address_name = '%s_name' % random_ascii(10) address_name = '%s_name' % random_ascii(10)
domain_name = '%sdomain.lan' % random_ascii(10) domain_name = '%sdomain.lan' % random_ascii(10)
address_domain = Domain.objects.create(name=domain_name, account=self.account) 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.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)) 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): class RESTListMixin(ListMixin):
@ -101,8 +180,23 @@ class RESTListMixin(ListMixin):
@save_response_on_error @save_response_on_error
def delete(self, name): def delete(self, name):
list = self.rest.lists.retrieve(name=name).get() self.rest.lists.retrieve(name=name).delete()
list.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): class AdminListMixin(ListMixin):
@ -111,48 +205,76 @@ class AdminListMixin(ListMixin):
self.admin_login() self.admin_login()
@snapshot_on_error @snapshot_on_error
def add(self, name, password, admin_email): def add(self, name, password, admin_email, address_name=None, address_domain=None):
url = self.live_server_url + reverse('admin:mails_List_add') url = self.live_server_url + reverse('admin:lists_list_add')
self.selenium.get(url) 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 = 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 = self.selenium.find_element_by_id('id_password1')
password_field.send_keys(password) password_field.send_keys(password)
password_field = self.selenium.find_element_by_id('id_password2') password_field = self.selenium.find_element_by_id('id_password2')
password_field.send_keys(password) password_field.send_keys(password)
if quota is not None: admin_email_field = self.selenium.find_element_by_id('id_admin_email')
quota_id = 'id_resources-resourcedata-content_type-object_id-0-allocated' admin_email_field.send_keys(admin_email)
quota_field = self.selenium.find_element_by_id(quota_id)
quota_field.clear()
quota_field.send_keys(quota)
if filtering is not None: if address_name:
filtering_input = self.selenium.find_element_by_id('id_filtering') address_name_field = self.selenium.find_element_by_id('id_address_name')
filtering_select = Select(filtering_input) address_name_field.send_keys(address_name)
filtering_select.select_by_value("CUSTOM")
filtering_inline = self.selenium.find_element_by_id('fieldsetcollapser0') domain = Domain.objects.get(name=address_domain)
filtering_inline.click() domain_input = self.selenium.find_element_by_id('id_address_domain')
time.sleep(0.5) domain_select = Select(domain_input)
filtering_field = self.selenium.find_element_by_id('id_custom_filtering') domain_select.select_by_value(str(domain.pk))
filtering_field.send_keys(filtering)
name_field.submit() name_field.submit()
self.assertNotEqual(url, self.selenium.current_url) 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): class RESTListTest(RESTListMixin, BaseLiveServerTestCase):
pass pass
#class AdminListTest(AdminListMixin, BaseLiveServerTestCase): class AdminListTest(AdminListMixin, BaseLiveServerTestCase):
# pass pass

View File

@ -233,6 +233,9 @@ class MailboxMixin(object):
sshrun(self.MASTER_SERVER, sshrun(self.MASTER_SERVER,
"grep '%s' %s/Maildir/.%s/new/*" % (token, home, folder), display=False) "grep '%s' %s/Maildir/.%s/new/*" % (token, home, folder), display=False)
# TODO test update shit
# TODO test autoreply
class RESTMailboxMixin(MailboxMixin): class RESTMailboxMixin(MailboxMixin):
def setUp(self): def setUp(self):

View File

@ -24,6 +24,8 @@ def BashSSH(backend, log, server, cmds):
log.script = script log.script = script
log.save(update_fields=['script']) log.save(update_fields=['script'])
logger.debug('%s is going to be executed on %s' % (backend, server)) logger.debug('%s is going to be executed on %s' % (backend, server))
channel = None
ssh = None
try: try:
# Avoid "Argument list too long" on large scripts by genereting a file # Avoid "Argument list too long" on large scripts by genereting a file
# and scping it to the remote server # and scping it to the remote server
@ -93,8 +95,10 @@ def BashSSH(backend, log, server, cmds):
logger.debug(log.traceback) logger.debug(log.traceback)
log.save() log.save()
finally: finally:
channel.close() if channel is not None:
ssh.close() channel.close()
if ssh is not None:
ssh.close()
def Python(backend, log, server, cmds): def Python(backend, log, server, cmds):

View File

@ -1,4 +1,3 @@
import copy
from threading import local from threading import local
from django.core.urlresolvers import resolve 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') @receiver(post_save, dispatch_uid='orchestration.post_save_collector')
def post_save_collector(sender, *args, **kwargs): def post_save_collector(sender, *args, **kwargs):
if sender != BackendLog: if sender not in [BackendLog, Operation]:
OperationsMiddleware.collect(Operation.SAVE, **kwargs) OperationsMiddleware.collect(Operation.SAVE, **kwargs)
@receiver(pre_delete, dispatch_uid='orchestration.pre_delete_collector') @receiver(pre_delete, dispatch_uid='orchestration.pre_delete_collector')
def pre_delete_collector(sender, *args, **kwargs): def pre_delete_collector(sender, *args, **kwargs):
if sender != BackendLog: if sender not in [BackendLog, Operation]:
OperationsMiddleware.collect(Operation.DELETE, **kwargs) OperationsMiddleware.collect(Operation.DELETE, **kwargs)
@ -49,7 +48,6 @@ class OperationsMiddleware(object):
request = getattr(cls.thread_locals, 'request', None) request = getattr(cls.thread_locals, 'request', None)
if request is None: if request is None:
return return
good_action = action
pending_operations = cls.get_pending_operations() pending_operations = cls.get_pending_operations()
for backend in ServiceBackend.get_backends(): for backend in ServiceBackend.get_backends():
instance = None instance = None
@ -84,15 +82,13 @@ class OperationsMiddleware(object):
break break
if not execute: if not execute:
continue continue
instance = copy.copy(instance)
good = instance
operation = Operation.create(backend, instance, action) operation = Operation.create(backend, instance, action)
if action != Operation.DELETE: if action != Operation.DELETE:
# usually we expect to be using last object state, # usually we expect to be using last object state,
# except when we are deleting it # except when we are deleting it
pending_operations.discard(operation) pending_operations.discard(operation)
pending_operations.add(operation) pending_operations.add(operation)
def process_request(self, request): def process_request(self, request):
""" Store request on a thread local variable """ """ Store request on a thread local variable """
type(self).thread_locals.request = request type(self).thread_locals.request = request

View File

@ -1,3 +1,4 @@
import copy
import socket import socket
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
@ -124,6 +125,9 @@ class BackendOperation(models.Model):
def create(cls, backend, instance, action): def create(cls, backend, instance, action):
op = cls(backend=backend.get_name(), instance=instance, action=action) op = cls(backend=backend.get_name(), instance=instance, action=action)
op.backend = backend 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 return op
@classmethod @classmethod

View File

@ -10,6 +10,7 @@ from django.core.management.base import CommandError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from selenium.webdriver.support.select import Select from selenium.webdriver.support.select import Select
from orchestra.admin.utils import change_url
from orchestra.apps.accounts.models import Account from orchestra.apps.accounts.models import Account
from orchestra.apps.orchestration.models import Server, Route from orchestra.apps.orchestration.models import Server, Route
from orchestra.utils.system import run, sshrun from orchestra.utils.system import run, sshrun
@ -268,8 +269,7 @@ class AdminSystemUserMixin(SystemUserMixin):
@snapshot_on_error @snapshot_on_error
def add_group(self, username, groupname): def add_group(self, username, groupname):
user = SystemUser.objects.get(username=username) user = SystemUser.objects.get(username=username)
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,)) url = self.live_server_url + change_url(user)
url = self.live_server_url + change
self.selenium.get(url) self.selenium.get(url)
groups = self.selenium.find_element_by_id('id_groups_add_all_link') groups = self.selenium.find_element_by_id('id_groups_add_all_link')
groups.click() groups.click()
@ -281,8 +281,7 @@ class AdminSystemUserMixin(SystemUserMixin):
@snapshot_on_error @snapshot_on_error
def save(self, username): def save(self, username):
user = SystemUser.objects.get(username=username) user = SystemUser.objects.get(username=username)
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,)) url = self.live_server_url + change_url(user)
url = self.live_server_url + change
self.selenium.get(url) self.selenium.get(url)
save = self.selenium.find_element_by_name('_save') save = self.selenium.find_element_by_name('_save')
save.submit() save.submit()
@ -367,3 +366,10 @@ class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase):
self.assertNotEqual(url, self.selenium.current_url) self.assertNotEqual(url, self.selenium.current_url)
self.assertRaises(ftplib.error_perm, self.validate_ftp, username, password) 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()

View File

@ -79,10 +79,7 @@ class RESTWebsiteMixin(RESTWebAppMixin):
@save_response_on_error @save_response_on_error
def delete_website(self, name): def delete_website(self, name):
print 'hola'
pass
self.rest.websites.retrieve(name=name).delete() self.rest.websites.retrieve(name=name).delete()
# self.rest.websites.retrieve(name=name).delete()
@save_response_on_error @save_response_on_error
def add_content(self, website, webapp, path): def add_content(self, website, webapp, path):
@ -94,6 +91,12 @@ class RESTWebsiteMixin(RESTWebAppMixin):
}) })
website.save() 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): class StaticRESTWebsiteTest(RESTWebsiteMixin, StaticWebAppMixin, WebsiteMixin, BaseLiveServerTestCase):
def test_mix_webapps(self): def test_mix_webapps(self):

View File

@ -168,7 +168,8 @@ function install_requirements () {
orchestra-orm==dev \ orchestra-orm==dev \
django-debug-toolbar==1.2.1 \ django-debug-toolbar==1.2.1 \
django-nose==1.2 \ django-nose==1.2 \
sqlparse" sqlparse
requests"
fi fi
# Make sure locales are in place before installing postgres # Make sure locales are in place before installing postgres

View File

@ -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 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