domains app functional tests passing
This commit is contained in:
parent
56ee1ba4a3
commit
9ecfc8d4dd
|
@ -59,7 +59,8 @@ Note `*` _for sustancial progress_
|
|||
|
||||
1. [ ] Integration with third-party service providers, e.g. Gandi
|
||||
2. [ ] Scheduling of service cancellations and deactivations
|
||||
1. [ ] Object level permissions system
|
||||
2. [ ] REST API for superusers
|
||||
3. [ ] Responsive user interface
|
||||
1. [ ] Object-level permission system
|
||||
2. [ ] REST API functionality for superusers
|
||||
3. [ ] Responsive user interface, based on a JS framework.
|
||||
4. [ ] Full documentation
|
||||
5. [ ] [http://www.ansible.com/home](Ansible) orchestration method, which synchronize the whole service config everytime instead of incremental changes.
|
||||
|
|
15
TODO.md
15
TODO.md
|
@ -141,7 +141,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
|||
|
||||
* Redirect junk emails and delete every 30 days?
|
||||
|
||||
* Complitely decouples scripts execution, billing, service definition
|
||||
* DOC: Complitely decouples scripts execution, billing, service definition
|
||||
|
||||
* Create SystemUser on account creation. username=username, is_main=True,
|
||||
* Exclude is_main=True from queryset filter default is_main=False
|
||||
|
@ -149,8 +149,15 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
|||
* Unify all users
|
||||
|
||||
|
||||
* backend message with link
|
||||
|
||||
* test fucking user
|
||||
* backend admin message with link
|
||||
|
||||
* delete main user -> delete account or prevent delete main user
|
||||
|
||||
|
||||
APPS app?
|
||||
|
||||
* https://blog.flameeyes.eu/2011/01/mostly-unknown-openssh-tricks
|
||||
|
||||
* Ansible orchestration *method* (methods.py)
|
||||
* interdependency user <-> account with the old usermodel
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ class AccountAdminMixin(object):
|
|||
filter_by_account_fields = []
|
||||
change_list_template = 'admin/accounts/account/change_list.html'
|
||||
change_form_template = 'admin/accounts/account/change_form.html'
|
||||
account = None
|
||||
|
||||
def account_link(self, instance):
|
||||
account = instance.account if instance.pk else self.account
|
||||
|
@ -151,7 +152,7 @@ class AccountAdminMixin(object):
|
|||
""" Filter by account """
|
||||
formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
if db_field.name in self.filter_by_account_fields:
|
||||
if hasattr(self, 'account'):
|
||||
if self.account:
|
||||
# Hack widget render in order to append ?account=id to the add url
|
||||
old_render = formfield.widget.render
|
||||
def render(*args, **kwargs):
|
||||
|
@ -161,6 +162,11 @@ class AccountAdminMixin(object):
|
|||
formfield.widget.render = render
|
||||
# Filter related object by account
|
||||
formfield.queryset = formfield.queryset.filter(account=self.account)
|
||||
elif db_field.name == 'account':
|
||||
if self.account:
|
||||
formfield.initial = self.account.pk
|
||||
elif Account.objects.count() == 1:
|
||||
formfield.initial = 1
|
||||
return formfield
|
||||
|
||||
def get_account_from_preserve_filters(self, request):
|
||||
|
@ -215,7 +221,7 @@ class SelectAccountAdminMixin(AccountAdminMixin):
|
|||
""" Provides support for accounts on ModelAdmin """
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
inlines = super(AccountAdminMixin, self).get_inline_instances(request, obj=obj)
|
||||
if hasattr(self, 'account'):
|
||||
if self.account:
|
||||
account = self.account
|
||||
else:
|
||||
account = Account.objects.get(pk=request.GET['account'])
|
||||
|
|
0
orchestra/apps/databases/tests/__init__.py
Normal file
0
orchestra/apps/databases/tests/__init__.py
Normal file
76
orchestra/apps/databases/tests/functional_tests/tests.py
Normal file
76
orchestra/apps/databases/tests/functional_tests/tests.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import MySQLdb
|
||||
from functools import partial
|
||||
|
||||
from django.conf import settings as djsettings
|
||||
from django.core.management.base import CommandError
|
||||
from django.core.urlresolvers import reverse
|
||||
from selenium.webdriver.support.select import Select
|
||||
|
||||
from orchestra.apps.accounts.models import Account
|
||||
from orchestra.apps.orchestration.models import Server, Route
|
||||
from orchestra.utils.system import run
|
||||
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii
|
||||
|
||||
from ... import backends, settings
|
||||
from ...models import Satabase
|
||||
|
||||
|
||||
class DatabaseTestMixin(object):
|
||||
MASTER_ADDR = 'localhost'
|
||||
DEPENDENCIES = (
|
||||
'orchestra.apps.orchestration',
|
||||
'orcgestra.apps.databases',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(SystemUserMixin, self).setUp()
|
||||
self.add_route()
|
||||
djsettings.DEBUG = True
|
||||
|
||||
def add_route(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def add(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def disable(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def add_group(self, username, groupname):
|
||||
raise NotImplementedError
|
||||
|
||||
def test_add(self):
|
||||
self.add()
|
||||
|
||||
|
||||
|
||||
|
||||
class MysqlBackendMixin(object):
|
||||
def add_route(self):
|
||||
server = Server.objects.create(name=self.MASTER_ADDR)
|
||||
backend = backends.MysqlBackend.get_name()
|
||||
Route.objects.create(backend=backend, match="database.type == 'mysql'", host=server)
|
||||
|
||||
def validate_create_table(self, name, username, password):
|
||||
db = MySQLdb.connect(host=self.MASTER_ADDR, user=username, passwd=password, db=name)
|
||||
cur = db.cursor()
|
||||
cur.execute('CREATE TABLE test;')
|
||||
|
||||
def validate_delete(self, name, username, password):
|
||||
self.asseRaises(MySQLdb.ConnectionError,
|
||||
MySQLdb.connect(host=self.MASTER_ADDR, user=username, passwd=password, db=name))
|
||||
|
||||
|
||||
|
||||
class RESTDatabaseTest(DatabaseTestMixin):
|
||||
def add(self, dbname):
|
||||
self.api.databases.create(name=dbname)
|
|
@ -33,7 +33,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
" { echo -e '%(conf)s' >> %(conf_path)s; UPDATED=1; }" % context)
|
||||
for subdomain in context['subdomains']:
|
||||
context['name'] = subdomain.name
|
||||
self.delete_conf(context)
|
||||
self.delete(subdomain)
|
||||
|
||||
def delete(self, domain):
|
||||
context = self.get_context(domain)
|
||||
|
@ -56,7 +56,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
context = {
|
||||
'name': domain.name,
|
||||
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
||||
'subdomains': domain.get_subdomains(),
|
||||
'subdomains': domain.subdomains.all(),
|
||||
'banner': self.get_banner(),
|
||||
}
|
||||
context.update({
|
||||
|
@ -92,7 +92,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
|||
context = {
|
||||
'name': domain.name,
|
||||
'masters': '; '.join(settings.DOMAINS_MASTERS),
|
||||
'subdomains': domain.get_subdomains()
|
||||
'subdomains': domain.subdomains.all()
|
||||
}
|
||||
context.update({
|
||||
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
||||
|
|
|
@ -16,8 +16,8 @@ class DomainAdminForm(forms.ModelForm):
|
|||
top = domain.get_top()
|
||||
if not top:
|
||||
# Fake an account to make django validation happy
|
||||
Account = self.fields['account']._queryset.model
|
||||
cleaned_data['account'] = Account()
|
||||
account_model = self.fields['account']._queryset.model
|
||||
cleaned_data['account'] = account_model()
|
||||
msg = _("An account should be provided for top domain names")
|
||||
raise ValidationError(msg)
|
||||
cleaned_data['account'] = top.account
|
||||
|
@ -37,20 +37,3 @@ class RecordInlineFormSet(forms.models.BaseInlineFormSet):
|
|||
records.append(data)
|
||||
domain = domain_for_validation(self.instance, records)
|
||||
validators.validate_zone(domain.render_zone())
|
||||
|
||||
|
||||
class DomainIterator(forms.models.ModelChoiceIterator):
|
||||
""" Group ticket owner by superusers, ticket.group and regular users """
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.account = kwargs.pop('account')
|
||||
self.domains = kwargs.pop('domains')
|
||||
super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
yield ('', '---------')
|
||||
account_domains = self.domains.filter(account=self.account)
|
||||
account_domains = account_domains.values_list('pk', 'name')
|
||||
yield (_("Account"), list(account_domains))
|
||||
domains = self.domains.exclude(account=self.account)
|
||||
domains = domains.values_list('pk', 'name')
|
||||
yield (_("Other"), list(domains))
|
||||
|
|
|
@ -12,13 +12,22 @@ def domain_for_validation(instance, records):
|
|||
for data in records:
|
||||
yield Record(type=data['type'], value=data['value'])
|
||||
domain.get_records = get_records
|
||||
|
||||
def get_top_subdomains(exclude=None):
|
||||
subdomains = []
|
||||
for subdomain in Domain.objects.filter(name__endswith='.%s' % domain.origin.name):
|
||||
if exclude != subdomain.pk:
|
||||
subdomain.top = domain
|
||||
yield subdomain
|
||||
domain.get_top_subdomains = get_top_subdomains
|
||||
|
||||
if domain.top:
|
||||
subdomains = domain.get_topsubdomains().exclude(pk=instance.pk)
|
||||
subdomains = domain.get_top_subdomains(exclude=instance.pk)
|
||||
domain.top.get_subdomains = lambda: list(subdomains) + [domain]
|
||||
elif not domain.pk:
|
||||
subdomains = []
|
||||
for subdomain in Domain.objects.filter(name__endswith=domain.name):
|
||||
subdomain.top = domain
|
||||
subdomains.append(subdomain)
|
||||
domain.get_subdomains = lambda: subdomains
|
||||
domain.get_subdomains = get_top_subdomains
|
||||
return domain
|
||||
|
|
|
@ -14,7 +14,7 @@ class Domain(models.Model):
|
|||
name = models.CharField(_("name"), max_length=256, unique=True,
|
||||
validators=[validate_hostname, validators.validate_allowed_domain])
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
related_name='domains', blank=True)
|
||||
related_name='domains', blank=True, help_text=_("Automatically selected for subdomains"))
|
||||
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomains')
|
||||
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial,
|
||||
help_text=_("Serial number"))
|
||||
|
@ -22,29 +22,32 @@ class Domain(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def origin(self):
|
||||
# Do not cache
|
||||
return self.top or self
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def is_top(self):
|
||||
# Do not cache
|
||||
return not bool(self.top)
|
||||
|
||||
def get_records(self):
|
||||
""" proxy method, needed for input validation """
|
||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||
return self.records.all()
|
||||
|
||||
def get_topsubdomains(self):
|
||||
""" proxy method, needed for input validation """
|
||||
def get_top_subdomains(self):
|
||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||
return self.origin.subdomains.all()
|
||||
|
||||
def get_subdomains(self):
|
||||
return self.get_topsubdomains().filter(name__regex=r'.%s$' % self.name)
|
||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||
return self.get_top_subdomains().filter(name__endswith=r'.%s' % self.name)
|
||||
|
||||
def render_zone(self):
|
||||
origin = self.origin
|
||||
zone = origin.render_records()
|
||||
for subdomain in origin.get_topsubdomains():
|
||||
for subdomain in origin.get_top_subdomains():
|
||||
zone += subdomain.render_records()
|
||||
return zone
|
||||
|
||||
|
@ -76,7 +79,7 @@ class Domain(models.Model):
|
|||
records.append(
|
||||
AttrDict(type=record.type, ttl=record.get_ttl(), value=record.value)
|
||||
)
|
||||
if not self.top:
|
||||
if self.is_top:
|
||||
if Record.NS not in types:
|
||||
for ns in settings.DOMAINS_DEFAULT_NS:
|
||||
records.append(AttrDict(type=Record.NS, value=ns))
|
||||
|
@ -129,7 +132,7 @@ class Domain(models.Model):
|
|||
for domain in domains.filter(name__endswith=self.name):
|
||||
domain.top = self
|
||||
domain.save(update_fields=['top'])
|
||||
self.get_subdomains().update(account=self.account)
|
||||
self.subdomains.update(account=self.account)
|
||||
|
||||
def get_top(self):
|
||||
split = self.name.split('.')
|
||||
|
|
|
@ -37,4 +37,3 @@ class DomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeria
|
|||
self._errors = { 'all': err.message }
|
||||
return None
|
||||
return instance
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ from django.conf import settings
|
|||
|
||||
|
||||
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
|
||||
'ns.example.com')
|
||||
'ns.orchestra.lan')
|
||||
|
||||
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
|
||||
'hostmaster@example.com')
|
||||
'hostmaster@orchestra.lan')
|
||||
|
||||
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h')
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import functools
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
|
||||
from django.conf import settings as djsettings
|
||||
from django.core.urlresolvers import reverse
|
||||
from selenium.webdriver.support.select import Select
|
||||
|
||||
from orchestra.apps.orchestration.models import Server, Route
|
||||
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii
|
||||
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii, snapshot_on_error
|
||||
from orchestra.utils.system import run
|
||||
|
||||
from ... import settings, utils, backends
|
||||
|
@ -16,10 +19,15 @@ run = functools.partial(run, display=False)
|
|||
|
||||
|
||||
class DomainTestMixin(object):
|
||||
MASTER_SERVER = os.environ.get('ORCHESTRA_MASTER_SERVER', 'localhost')
|
||||
SLAVE_SERVER = os.environ.get('ORCHESTRA_SLAVE_SERVER', 'localhost')
|
||||
MASTER_SERVER_ADDR = socket.gethostbyname(MASTER_SERVER)
|
||||
SLAVE_SERVER_ADDR = socket.gethostbyname(SLAVE_SERVER)
|
||||
|
||||
def setUp(self):
|
||||
djsettings.DEBUG = True
|
||||
settings.DOMAINS_MASTERS = [self.MASTER_SERVER_ADDR]
|
||||
super(DomainTestMixin, self).setUp()
|
||||
self.MASTER_ADDR = os.environ['ORCHESTRA_DNS_MASTER_ADDR']
|
||||
self.SLAVE_ADDR = os.environ['ORCHESTRA_DNS_SLAVE_ADDR']
|
||||
self.domain_name = 'orchestra%s.lan' % random_ascii(10)
|
||||
self.domain_records = (
|
||||
(Record.MX, '10 mail.orchestra.lan.'),
|
||||
|
@ -33,19 +41,19 @@ class DomainTestMixin(object):
|
|||
(Record.NS, 'ns1.%s.' % self.domain_name),
|
||||
(Record.NS, 'ns2.%s.' % self.domain_name),
|
||||
)
|
||||
self.subdomain1_name = 'ns1.%s' % self.domain_name
|
||||
self.subdomain1_records = (
|
||||
(Record.A, '%s' % self.SLAVE_ADDR),
|
||||
self.ns1_name = 'ns1.%s' % self.domain_name
|
||||
self.ns1_records = (
|
||||
(Record.A, '%s' % self.SLAVE_SERVER_ADDR),
|
||||
)
|
||||
self.subdomain2_name = 'ns2.%s' % self.domain_name
|
||||
self.subdomain2_records = (
|
||||
(Record.A, '%s' % self.MASTER_ADDR),
|
||||
self.ns2_name = 'ns2.%s' % self.domain_name
|
||||
self.ns2_records = (
|
||||
(Record.A, '%s' % self.MASTER_SERVER_ADDR),
|
||||
)
|
||||
self.subdomain3_name = 'www.%s' % self.domain_name
|
||||
self.subdomain3_records = (
|
||||
self.www_name = 'www.%s' % self.domain_name
|
||||
self.www_records = (
|
||||
(Record.CNAME, 'external.server.org.'),
|
||||
)
|
||||
self.second_domain_name = 'django%s.lan' % random_ascii(10)
|
||||
self.django_domain_name = 'django%s.lan' % random_ascii(10)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
|
@ -173,42 +181,47 @@ class DomainTestMixin(object):
|
|||
self.assertEqual('external.server.org.', cname[4])
|
||||
|
||||
def test_add(self):
|
||||
self.add(self.subdomain1_name, self.subdomain1_records)
|
||||
self.add(self.subdomain2_name, self.subdomain2_records)
|
||||
self.add(self.ns1_name, self.ns1_records)
|
||||
self.add(self.ns2_name, self.ns2_records)
|
||||
self.add(self.domain_name, self.domain_records)
|
||||
self.validate_add(self.MASTER_ADDR, self.domain_name)
|
||||
self.validate_add(self.SLAVE_ADDR, self.domain_name)
|
||||
self.validate_add(self.MASTER_SERVER_ADDR, self.domain_name)
|
||||
time.sleep(0.5)
|
||||
self.validate_add(self.SLAVE_SERVER_ADDR, self.domain_name)
|
||||
|
||||
def test_delete(self):
|
||||
self.add(self.subdomain1_name, self.subdomain1_records)
|
||||
self.add(self.subdomain2_name, self.subdomain2_records)
|
||||
self.add(self.ns1_name, self.ns1_records)
|
||||
self.add(self.ns2_name, self.ns2_records)
|
||||
self.add(self.domain_name, self.domain_records)
|
||||
self.delete(self.domain_name)
|
||||
for name in [self.domain_name, self.subdomain1_name, self.subdomain2_name]:
|
||||
self.validate_delete(self.MASTER_ADDR, name)
|
||||
self.validate_delete(self.SLAVE_ADDR, name)
|
||||
for name in [self.domain_name, self.ns1_name, self.ns2_name]:
|
||||
self.validate_delete(self.MASTER_SERVER_ADDR, name)
|
||||
self.validate_delete(self.SLAVE_SERVER_ADDR, name)
|
||||
|
||||
def test_update(self):
|
||||
self.add(self.subdomain1_name, self.subdomain1_records)
|
||||
self.add(self.subdomain2_name, self.subdomain2_records)
|
||||
self.add(self.ns1_name, self.ns1_records)
|
||||
self.add(self.ns2_name, self.ns2_records)
|
||||
self.add(self.domain_name, self.domain_records)
|
||||
self.update(self.domain_name, self.domain_update_records)
|
||||
self.add(self.subdomain3_name, self.subdomain3_records)
|
||||
self.validate_update(self.MASTER_ADDR, self.domain_name)
|
||||
self.add(self.www_name, self.www_records)
|
||||
self.validate_update(self.MASTER_SERVER_ADDR, self.domain_name)
|
||||
time.sleep(5)
|
||||
self.validate_update(self.SLAVE_ADDR, self.domain_name)
|
||||
self.validate_update(self.SLAVE_SERVER_ADDR, self.domain_name)
|
||||
|
||||
def test_add_add_delete_delete(self):
|
||||
self.add(self.subdomain1_name, self.subdomain1_records)
|
||||
self.add(self.subdomain2_name, self.subdomain2_records)
|
||||
self.add(self.ns1_name, self.ns1_records)
|
||||
self.add(self.ns2_name, self.ns2_records)
|
||||
self.add(self.domain_name, self.domain_records)
|
||||
self.add(self.second_domain_name, self.domain_records)
|
||||
self.add(self.django_domain_name, self.domain_records)
|
||||
self.delete(self.domain_name)
|
||||
self.validate_add(self.MASTER_ADDR, self.second_domain_name)
|
||||
self.validate_add(self.SLAVE_ADDR, self.second_domain_name)
|
||||
self.delete(self.second_domain_name)
|
||||
self.validate_delete(self.MASTER_ADDR, self.second_domain_name)
|
||||
self.validate_delete(self.SLAVE_ADDR, self.second_domain_name)
|
||||
self.validate_add(self.MASTER_SERVER_ADDR, self.django_domain_name)
|
||||
self.validate_add(self.SLAVE_SERVER_ADDR, self.django_domain_name)
|
||||
self.delete(self.django_domain_name)
|
||||
self.validate_delete(self.MASTER_SERVER_ADDR, self.django_domain_name)
|
||||
self.validate_delete(self.SLAVE_SERVER_ADDR, self.django_domain_name)
|
||||
|
||||
def test_bad_creation(self):
|
||||
self.assertRaises((self.rest.ResponseStatusError, AssertionError),
|
||||
self.add, self.domain_name, self.domain_records)
|
||||
|
||||
|
||||
class AdminDomainMixin(DomainTestMixin):
|
||||
|
@ -229,27 +242,38 @@ class AdminDomainMixin(DomainTestMixin):
|
|||
value_input.send_keys(value)
|
||||
return value_input
|
||||
|
||||
@snapshot_on_error
|
||||
def add(self, domain_name, records):
|
||||
# TODO use reverse
|
||||
url = self.live_server_url + '/admin/domains/domain/add/'
|
||||
add = reverse('admin:domains_domain_add')
|
||||
url = self.live_server_url + add
|
||||
self.selenium.get(url)
|
||||
|
||||
name = self.selenium.find_element_by_id('id_name')
|
||||
name.send_keys(domain_name)
|
||||
|
||||
account_input = self.selenium.find_element_by_id('id_account')
|
||||
account_select = Select(account_input)
|
||||
account_select.select_by_value(str(self.account.pk))
|
||||
|
||||
value_input = self._add_records(records)
|
||||
value_input.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def delete(self, domain_name):
|
||||
domain = Domain.objects.get(name=domain_name)
|
||||
url = self.live_server_url + '/admin/domains/domain/%d/delete/' % domain.pk
|
||||
delete = reverse('admin:domains_domain_delete', args=(domain.pk,))
|
||||
url = self.live_server_url + delete
|
||||
self.selenium.get(url)
|
||||
form = self.selenium.find_element_by_name('post')
|
||||
form.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def update(self, domain_name, records):
|
||||
domain = Domain.objects.get(name=domain_name)
|
||||
url = self.live_server_url + '/admin/domains/domain/%d/' % domain.pk
|
||||
change = reverse('admin:domains_domain_change', args=(domain.pk,))
|
||||
url = self.live_server_url + change
|
||||
self.selenium.get(url)
|
||||
value_input = self._add_records(records)
|
||||
value_input.submit()
|
||||
|
@ -284,10 +308,10 @@ class Bind9BackendMixin(object):
|
|||
)
|
||||
|
||||
def add_route(self):
|
||||
master = Server.objects.create(name=self.MASTER_ADDR)
|
||||
master = Server.objects.create(name=self.MASTER_SERVER, address=self.MASTER_SERVER_ADDR)
|
||||
backend = backends.Bind9MasterDomainBackend.get_name()
|
||||
Route.objects.create(backend=backend, match=True, host=master)
|
||||
slave = Server.objects.create(name=self.SLAVE_ADDR)
|
||||
slave = Server.objects.create(name=self.SLAVE_SERVER, address=self.SLAVE_SERVER_ADDR)
|
||||
backend = backends.Bind9SlaveDomainBackend.get_name()
|
||||
Route.objects.create(backend=backend, match=True, host=slave)
|
||||
|
||||
|
@ -296,5 +320,5 @@ class RESTBind9BackendDomainTest(Bind9BackendMixin, RESTDomainMixin, BaseLiveSer
|
|||
pass
|
||||
|
||||
|
||||
class AdminBind9BackendDomainest(Bind9BackendMixin, AdminDomainMixin, BaseLiveServerTestCase):
|
||||
class AdminBind9BackendDomainTest(Bind9BackendMixin, AdminDomainMixin, BaseLiveServerTestCase):
|
||||
pass
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from django.contrib import messages
|
||||
from django.core.mail import mail_admins
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
|
||||
|
||||
def send_report(method, args, log):
|
||||
|
@ -32,15 +34,27 @@ def send_report(method, args, log):
|
|||
|
||||
|
||||
def message_user(request, logs):
|
||||
total = len(logs)
|
||||
successes = [ log for log in logs if log.state == log.SUCCESS ]
|
||||
successes = len(successes)
|
||||
total, successes = 0, 0
|
||||
ids = []
|
||||
for log in logs:
|
||||
total += 1
|
||||
ids.append(log.pk)
|
||||
if log.state == log.SUCCESS:
|
||||
successes += 1
|
||||
errors = total-successes
|
||||
if errors:
|
||||
msg = 'backends have' if errors > 1 else 'backend has'
|
||||
msg = _("%d out of %d {0} fail to execute".format(msg))
|
||||
messages.warning(request, msg % (errors, total))
|
||||
if total > 1:
|
||||
url = reverse('admin:orchestration_backendlog_changelist')
|
||||
url += '?id__in=%s' ','.join(map(str, ids))
|
||||
else:
|
||||
msg = 'backends have' if successes > 1 else 'backend has'
|
||||
msg = _("%d {0} been successfully executed".format(msg))
|
||||
messages.success(request, msg % successes)
|
||||
url = reverse('admin:orchestration_backendlog_change', args=ids)
|
||||
if errors:
|
||||
msg = ungettext(
|
||||
_('{errors} out of {total} <a href="{url}">banckends</a> has fail to execute.'),
|
||||
_('{errors} out of {total} <a href="{url}">banckends</a> have fail to execute.'),
|
||||
errors)
|
||||
else:
|
||||
msg = ungettext(
|
||||
_('{total} <a href="{url}">banckend</a> has been executed.'),
|
||||
_('{total} <a href="{url}">banckends</a> have been executed.'),
|
||||
total)
|
||||
messages.warning(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
from threading import local
|
||||
|
||||
from django.core.urlresolvers import resolve
|
||||
from django.db.models.signals import pre_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
from django.http.response import HttpResponseServerError
|
||||
|
@ -92,6 +93,6 @@ class OperationsMiddleware(object):
|
|||
operations = type(self).get_pending_operations()
|
||||
if operations:
|
||||
logs = Operation.execute(operations)
|
||||
if logs:
|
||||
if logs and resolve(request.path).app_name == 'admin':
|
||||
message_user(request, logs)
|
||||
return response
|
||||
|
|
|
@ -132,4 +132,3 @@ class FTPTraffic(ServiceMonitor):
|
|||
'object_id': user.pk,
|
||||
'username': user.username,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import ftplib
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from functools import partial
|
||||
|
||||
import paramiko
|
||||
|
@ -10,19 +12,19 @@ from selenium.webdriver.support.select import Select
|
|||
|
||||
from orchestra.apps.accounts.models import Account
|
||||
from orchestra.apps.orchestration.models import Server, Route
|
||||
from orchestra.utils.system import run
|
||||
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii
|
||||
from orchestra.utils.system import run, sshrun
|
||||
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii, snapshot_on_error
|
||||
|
||||
from ... import backends, settings
|
||||
from ...models import SystemUser
|
||||
|
||||
|
||||
r = partial(run, silent=True, display=False)
|
||||
sshr = partial(sshrun, silent=True, display=False)
|
||||
|
||||
|
||||
class SystemUserMixin(object):
|
||||
MASTER_ADDR = 'localhost'
|
||||
ACCOUNT_USERNAME = '%s_account' % random_ascii(10)
|
||||
MASTER_SERVER = os.environ.get('ORCHESTRA_MASTER_SERVER', 'localhost')
|
||||
DEPENDENCIES = (
|
||||
'orchestra.apps.orchestration',
|
||||
'orcgestra.apps.systemusers',
|
||||
|
@ -34,7 +36,7 @@ class SystemUserMixin(object):
|
|||
djsettings.DEBUG = True
|
||||
|
||||
def add_route(self):
|
||||
master = Server.objects.create(name=self.MASTER_ADDR)
|
||||
master = Server.objects.create(name=self.MASTER_SERVER)
|
||||
backend = backends.SystemUserBackend.get_name()
|
||||
Route.objects.create(backend=backend, match=True, host=master)
|
||||
|
||||
|
@ -57,7 +59,7 @@ class SystemUserMixin(object):
|
|||
raise NotImplementedError
|
||||
|
||||
def validate_user(self, username):
|
||||
idcmd = r("id %s" % username)
|
||||
idcmd = sshr(self.MASTER_SERVER, "id %s" % username)
|
||||
self.assertEqual(0, idcmd.return_code)
|
||||
user = SystemUser.objects.get(username=username)
|
||||
groups = list(user.groups.values_list('username', flat=True))
|
||||
|
@ -68,18 +70,22 @@ class SystemUserMixin(object):
|
|||
|
||||
def validate_delete(self, username):
|
||||
self.assertRaises(SystemUser.DoesNotExist, SystemUser.objects.get, username=username)
|
||||
self.assertRaises(CommandError, run, 'id %s' % username, display=False)
|
||||
self.assertRaises(CommandError, run, 'grep "^%s:" /etc/groups' % username, display=False)
|
||||
self.assertRaises(CommandError, run, 'grep "^%s:" /etc/passwd' % username, display=False)
|
||||
self.assertRaises(CommandError, run, 'grep "^%s:" /etc/shadow' % username, display=False)
|
||||
self.assertRaises(CommandError,
|
||||
sshrun, self.MASTER_SERVER,'id %s' % username, display=False)
|
||||
self.assertRaises(CommandError,
|
||||
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/groups' % username, display=False)
|
||||
self.assertRaises(CommandError,
|
||||
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/passwd' % username, display=False)
|
||||
self.assertRaises(CommandError,
|
||||
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/shadow' % username, display=False)
|
||||
|
||||
def validate_ftp(self, username, password):
|
||||
connection = ftplib.FTP(self.MASTER_ADDR)
|
||||
connection = ftplib.FTP(self.MASTER_SERVER)
|
||||
connection.login(user=username, passwd=password)
|
||||
connection.close()
|
||||
|
||||
def validate_sftp(self, username, password):
|
||||
transport = paramiko.Transport((self.MASTER_ADDR, 22))
|
||||
transport = paramiko.Transport((self.MASTER_SERVER, 22))
|
||||
transport.connect(username=username, password=password)
|
||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||
sftp.listdir()
|
||||
|
@ -88,14 +94,14 @@ class SystemUserMixin(object):
|
|||
def validate_ssh(self, username, password):
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(self.MASTER_ADDR, username=username, password=password)
|
||||
ssh.connect(self.MASTER_SERVER, username=username, password=password)
|
||||
transport = ssh.get_transport()
|
||||
channel = transport.open_session()
|
||||
channel.exec_command('ls')
|
||||
self.assertEqual(0, channel.recv_exit_status())
|
||||
channel.close()
|
||||
|
||||
def test_create_systemuser(self):
|
||||
def test_create(self):
|
||||
username = '%s_systemuser' % random_ascii(10)
|
||||
password = '@!?%spppP001' % random_ascii(5)
|
||||
self.add(username, password)
|
||||
|
@ -125,7 +131,7 @@ class SystemUserMixin(object):
|
|||
self.addCleanup(partial(self.delete, username))
|
||||
self.validate_ssh(username, password)
|
||||
|
||||
def test_delete_systemuser(self):
|
||||
def test_delete(self):
|
||||
username = '%s_systemuser' % random_ascii(10)
|
||||
password = '@!?%sppppP001' % random_ascii(5)
|
||||
self.add(username, password)
|
||||
|
@ -133,7 +139,7 @@ class SystemUserMixin(object):
|
|||
self.delete(username)
|
||||
self.validate_delete(username)
|
||||
|
||||
def test_add_group_systemuser(self):
|
||||
def test_add_group(self):
|
||||
username = '%s_systemuser' % random_ascii(10)
|
||||
password = '@!?%spppP001' % random_ascii(5)
|
||||
self.add(username, password)
|
||||
|
@ -150,7 +156,7 @@ class SystemUserMixin(object):
|
|||
self.assertIn(username2, groups)
|
||||
self.validate_user(username)
|
||||
|
||||
def test_disable_systemuser(self):
|
||||
def test_disable(self):
|
||||
username = '%s_systemuser' % random_ascii(10)
|
||||
password = '@!?%spppP001' % random_ascii(5)
|
||||
self.add(username, password, shell='/dev/null')
|
||||
|
@ -159,6 +165,10 @@ class SystemUserMixin(object):
|
|||
self.disable(username)
|
||||
self.validate_user(username)
|
||||
self.assertRaises(ftplib.error_perm, self.validate_ftp, username, password)
|
||||
|
||||
def test_change_password(self):
|
||||
pass
|
||||
# TODO
|
||||
|
||||
|
||||
class RESTSystemUserMixin(SystemUserMixin):
|
||||
|
@ -200,6 +210,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
|||
self.save(self.account.username)
|
||||
self.addCleanup(partial(self.delete, self.account.username))
|
||||
|
||||
@snapshot_on_error
|
||||
def add(self, username, password, shell='/dev/null'):
|
||||
url = self.live_server_url + reverse('admin:systemusers_systemuser_add')
|
||||
self.selenium.get(url)
|
||||
|
@ -223,6 +234,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
|||
username_field.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def delete(self, username):
|
||||
user = SystemUser.objects.get(username=username)
|
||||
delete = reverse('admin:systemusers_systemuser_delete', args=(user.pk,))
|
||||
|
@ -232,6 +244,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
|||
confirmation.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def disable(self, username):
|
||||
user = SystemUser.objects.get(username=username)
|
||||
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,))
|
||||
|
@ -243,6 +256,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
|||
save.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def add_group(self, username, groupname):
|
||||
user = SystemUser.objects.get(username=username)
|
||||
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,))
|
||||
|
@ -254,6 +268,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
|||
save.submit()
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
|
||||
@snapshot_on_error
|
||||
def save(self, username):
|
||||
user = SystemUser.objects.get(username=username)
|
||||
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,))
|
||||
|
@ -269,6 +284,7 @@ class RESTSystemUserTest(RESTSystemUserMixin, BaseLiveServerTestCase):
|
|||
|
||||
|
||||
class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase):
|
||||
@snapshot_on_error
|
||||
def test_create_account(self):
|
||||
url = self.live_server_url + reverse('admin:accounts_account_add')
|
||||
self.selenium.get(url)
|
||||
|
@ -298,8 +314,9 @@ class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase):
|
|||
account = Account.objects.get(username=account_username)
|
||||
self.addCleanup(account.delete)
|
||||
self.assertNotEqual(url, self.selenium.current_url)
|
||||
self.assertEqual(0, r("id %s" % account.username).return_code)
|
||||
self.assertEqual(0, sshr(self.MASTER_SERVER, "id %s" % account.username).return_code)
|
||||
|
||||
@snapshot_on_error
|
||||
def test_delete_account(self):
|
||||
home = self.account.systemusers.get(is_main=True).get_home()
|
||||
|
||||
|
|
|
@ -158,7 +158,8 @@ function install_requirements () {
|
|||
PIP="${PIP} \
|
||||
selenium \
|
||||
xvfbwrapper \
|
||||
freezegun"
|
||||
freezegun \
|
||||
coverage"
|
||||
fi
|
||||
|
||||
# Make sure locales are in place before installing postgres
|
||||
|
|
|
@ -105,6 +105,11 @@ def run(command, display=True, error_codes=[0], silent=False, stdin=''):
|
|||
return out
|
||||
|
||||
|
||||
def sshrun(addr, command, *args, **kwargs):
|
||||
cmd = "ssh -o stricthostkeychecking=no root@%s -C '%s'" % (addr, command)
|
||||
return run(cmd, *args, **kwargs)
|
||||
|
||||
|
||||
def get_default_celeryd_username():
|
||||
""" Introspect celeryd defaults file in order to get its username """
|
||||
user = None
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import datetime
|
||||
import string
|
||||
import random
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, get_user_model
|
||||
|
@ -105,3 +107,17 @@ class BaseLiveServerTestCase(AppDependencyMixin, LiveServerTestCase):
|
|||
|
||||
def rest_login(self):
|
||||
self.rest.login(username=self.account.username, password=self.account_password)
|
||||
|
||||
|
||||
def snapshot_on_error(test):
|
||||
@wraps(test)
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
test(*args, **kwargs)
|
||||
except:
|
||||
self = args[0]
|
||||
timestamp = datetime.datetime.now().isoformat().replace(':', '')
|
||||
filename = '/tmp/screenshot_%s_%s.png' % (self.id(), timestamp)
|
||||
self.selenium.save_screenshot(filename)
|
||||
raise
|
||||
return inner
|
||||
|
|
24
scripts/services/bind9.md
Normal file
24
scripts/services/bind9.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
Bind9 Master and Slave
|
||||
======================
|
||||
|
||||
1. Install bind9 service as well as some convinient utilities on master and slave servers
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install bind9 dnsutils
|
||||
```
|
||||
|
||||
2. create the zone directory on the master server
|
||||
```bash
|
||||
mkdir /etc/bind/master
|
||||
chown bind.bind /etc/bind/master
|
||||
```
|
||||
|
||||
2. Allow zone transfer on master by adding the following line to `named.conf.options`
|
||||
```bash
|
||||
allow-transfer { slave-ip; };
|
||||
```
|
||||
|
||||
3. Addlow notifications on the slave server by adding the following line to `named.conf.options`
|
||||
```bash
|
||||
allow-notify { master-ip; };
|
||||
```
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Installs and confingures bind9 to work with Orchestra
|
||||
|
||||
|
||||
apt-get update
|
||||
apt-get install bind9
|
||||
|
||||
echo "nameserver 127.0.0.1" > /etc/resolv.conf
|
|
@ -9,6 +9,15 @@ apt-get install postfix
|
|||
|
||||
|
||||
apt-get install dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sieve
|
||||
sed -i "s#^mail_location = mbox.*#mail_location = maildir:~/Maildir#" /etc/dovecot/conf.d/10-mail.conf
|
||||
echo 'auth_username_format = %n' >> /etc/dovecot/conf.d/10-auth.conf
|
||||
echo 'service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}' >> /etc/dovecot/conf.d/10-master.conf
|
||||
|
||||
|
||||
cat > /etc/apt/sources.list.d/mailscanner.list << 'EOF'
|
||||
|
@ -18,16 +27,17 @@ EOF
|
|||
|
||||
wget -O - http://apt.baruwa.org/baruwa-apt-keys.gpg | apt-key add -
|
||||
|
||||
|
||||
apt-get update
|
||||
apt-get install mailscanner
|
||||
|
||||
|
||||
|
||||
|
||||
apt-get install dovecot-core dovecot-imapd dovecot-pop3d dovecot-sieve
|
||||
apt-get install postfix
|
||||
echo 'home_mailbox = Maildir/' >> /etc/postfix/main.cf
|
||||
echo 'mailbox_transport = lmtp:unix:private/dovecot-lmtp' >> /etc/postfix/main.cf
|
||||
|
||||
|
||||
mail_location = maildir:~/Maildir
|
||||
|
||||
/etc/init.d/dovecot restart
|
||||
/etc/init.d/postfix restart
|
||||
|
||||
|
|
|
@ -12,6 +12,6 @@ Restricted Shell for SCP and Rsync
|
|||
|
||||
2. Enable the shell
|
||||
```bash
|
||||
ln -s /usr/local/bin/rssh /bin/rssh
|
||||
ln -s /usr/bin/rssh /bin/rssh
|
||||
echo /bin/rssh >> /etc/shells
|
||||
```
|
||||
|
|
|
@ -12,9 +12,9 @@ VsFTPd with System Users
|
|||
```bash
|
||||
sed -i "s/anonymous_enable=YES/anonymous_enable=NO/" /etc/vsftpd.conf
|
||||
sed -i "s/#local_enable=YES/local_enable=YES/" /etc/vsftpd.conf
|
||||
sed -i "s/#write_enable=YES/write_enable=YES" /etc/vsftpd.conf
|
||||
sed -i "s/#chroot_local_user=YES/chroot_local_user=YES/" /etc/vsftpd.conf
|
||||
|
||||
sed -i "s/#write_enable=YES/write_enable=YES/" /etc/vsftpd.conf
|
||||
# sed -i "s/#chroot_local_user=YES/chroot_local_user=YES/" /etc/vsftpd.conf
|
||||
|
||||
echo '/dev/null' >> /etc/shells
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in a new issue