lists functional tests passing
This commit is contained in:
parent
4c7c5b5505
commit
3e246f9fe0
4
TODO.md
4
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
|
* 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!
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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',)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue