Removed slaves domain settings, now generated on the fly

This commit is contained in:
Marc 2014-10-03 17:37:36 +00:00
parent 9ecfc8d4dd
commit 6a3e3f637c
14 changed files with 88 additions and 44 deletions

View file

@ -15,7 +15,7 @@ Note `*` _for sustancial progress_
4. [ ] Data model, crazy input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of: 4. [ ] Data model, crazy input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
1. [x] PHP/static Web applications 1. [x] PHP/static Web applications
1. [x] Websites with Apache 1. [x] Websites with Apache
2. [ ] *FTP/rsync/scp/shell system accounts 2. [x] FTP/rsync/scp/shell system accounts
2. [ ] *Databases and database users with MySQL 2. [ ] *Databases and database users with MySQL
1. [ ] *Mail accounts, aliases, forwards with Postfix and Dovecot 1. [ ] *Mail accounts, aliases, forwards with Postfix and Dovecot
1. [x] DNS with Bind 1. [x] DNS with Bind
@ -63,4 +63,4 @@ Note `*` _for sustancial progress_
2. [ ] REST API functionality for superusers 2. [ ] REST API functionality for superusers
3. [ ] Responsive user interface, based on a JS framework. 3. [ ] Responsive user interface, based on a JS framework.
4. [ ] Full documentation 4. [ ] Full documentation
5. [ ] [http://www.ansible.com/home](Ansible) orchestration method, which synchronize the whole service config everytime instead of incremental changes. 5. [ ] [Ansible](http://www.ansible.com/home) orchestration method, which synchronizes the whole service config everytime instead of incremental changes.

View file

@ -1,9 +1,11 @@
import textwrap
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from . import settings
from orchestra.apps.orchestration import ServiceController from orchestra.apps.orchestration import ServiceController
from . import settings
class Bind9MasterDomainBackend(ServiceController): class Bind9MasterDomainBackend(ServiceController):
verbose_name = _("Bind9 master domain") verbose_name = _("Bind9 master domain")
@ -52,20 +54,31 @@ class Bind9MasterDomainBackend(ServiceController):
""" reload bind if needed """ """ reload bind if needed """
self.append('[[ $UPDATED == 1 ]] && service bind9 reload') self.append('[[ $UPDATED == 1 ]] && service bind9 reload')
def get_servers(self, domain, backend):
from orchestra.apps.orchestration.models import Route, BackendOperation as Operation
operation = Operation(backend=backend, action='save', instance=domain)
servers = []
for server in Route.get_servers(operation):
servers.append(server.get_ip())
return servers
def get_context(self, domain): def get_context(self, domain):
context = { context = {
'name': domain.name, 'name': domain.name,
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name}, 'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
'subdomains': domain.subdomains.all(), 'subdomains': domain.subdomains.all(),
'banner': self.get_banner(), 'banner': self.get_banner(),
'slaves': '; '.join(self.get_servers(domain, Bind9SlaveDomainBackend)),
} }
context.update({ context.update({
'conf_path': settings.DOMAINS_MASTERS_PATH, 'conf_path': settings.DOMAINS_MASTERS_PATH,
'conf': 'zone "%(name)s" {\n' 'conf': textwrap.dedent("""
' // %(banner)s\n' zone "%(name)s" {
' type master;\n' // %(banner)s
' file "%(zone_path)s";\n' type master;
'};\n' % context file "%(zone_path)s";
allow-transfer { %(slaves)s; };
};""" % context)
}) })
return context return context
@ -91,15 +104,19 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
def get_context(self, domain): def get_context(self, domain):
context = { context = {
'name': domain.name, 'name': domain.name,
'masters': '; '.join(settings.DOMAINS_MASTERS), 'banner': self.get_banner(),
'subdomains': domain.subdomains.all() 'subdomains': domain.subdomains.all(),
'masters': '; '.join(self.get_servers(domain, Bind9MasterDomainBackend)),
} }
context.update({ context.update({
'conf_path': settings.DOMAINS_SLAVES_PATH, 'conf_path': settings.DOMAINS_SLAVES_PATH,
'conf': 'zone "%(name)s" {\n' 'conf': textwrap.dedent("""
' type slave;\n' zone "%(name)s" {
' file "%(name)s";\n' // %(banner)s
' masters { %(masters)s; };\n' type slave;
'};\n' % context file "%(name)s";
masters { %(masters)s; };
allow-notify { %(masters)s; };
};""" % context)
}) })
return context return context

View file

@ -124,6 +124,7 @@ class Domain(models.Model):
top = self.get_top() top = self.get_top()
if top: if top:
self.top = top self.top = top
self.account_id = self.account_id or top.account_id
else: else:
update = True update = True
super(Domain, self).save(*args, **kwargs) super(Domain, self).save(*args, **kwargs)
@ -132,7 +133,7 @@ class Domain(models.Model):
for domain in domains.filter(name__endswith=self.name): for domain in domains.filter(name__endswith=self.name):
domain.top = self domain.top = self
domain.save(update_fields=['top']) domain.save(update_fields=['top'])
self.subdomains.update(account=self.account) self.subdomains.update(account_id=self.account_id)
def get_top(self): def get_top(self):
split = self.name.split('.') split = self.name.split('.')

View file

@ -4,48 +4,61 @@ from django.conf import settings
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER', DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
'ns.orchestra.lan') 'ns.orchestra.lan')
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER', DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
'hostmaster@orchestra.lan') 'hostmaster@orchestra.lan')
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h') DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h')
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d') DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d')
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h') DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h')
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w') DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w')
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h') DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h')
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s') DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s')
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local') DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local')
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local') DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local')
DOMAINS_MASTERS = getattr(settings, 'DOMAINS_MASTERS', ['10.0.3.13'])
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH', DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
'/usr/sbin/named-checkzone -i local') '/usr/sbin/named-checkzone -i local')
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm') DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13') DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', ( DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
'10 mail.orchestra.lan.', '10 mail.orchestra.lan.',
'10 mail2.orchestra.lan.', '10 mail2.orchestra.lan.',
)) ))
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', ( DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
'ns1.orchestra.lan.', 'ns1.orchestra.lan.',
'ns2.orchestra.lan.', 'ns2.orchestra.lan.',
)) ))
DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN', DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
# This setting prevents users from providing random domain names, i.e. google.com # This setting prevents users from providing random domain names, i.e. google.com
# You can generate a 5K forbidden domains list from Alexa's top 1M # You can generate a 5K forbidden domains list from Alexa's top 1M
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip # wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list # unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
# '%(site_root)s/forbidden_domains.list') # '%(site_root)s/forbidden_domains.list')
'') '')

View file

@ -26,7 +26,6 @@ class DomainTestMixin(object):
def setUp(self): def setUp(self):
djsettings.DEBUG = True djsettings.DEBUG = True
settings.DOMAINS_MASTERS = [self.MASTER_SERVER_ADDR]
super(DomainTestMixin, self).setUp() super(DomainTestMixin, self).setUp()
self.domain_name = 'orchestra%s.lan' % random_ascii(10) self.domain_name = 'orchestra%s.lan' % random_ascii(10)
self.domain_records = ( self.domain_records = (

View file

@ -1,17 +1,18 @@
from django.test import TestCase from orchestra.utils.tests import BaseTestCase
from ..models import Domain from ..models import Domain
class DomainTests(TestCase): class DomainTest(BaseTestCase):
def setUp(self): def test_top_relation(self):
self.domain = Domain.objects.create(name='rostrepalid.org') account = self.create_account()
domain = Domain.objects.create(name='rostrepalid.org', account=account)
Domain.objects.create(name='www.rostrepalid.org') Domain.objects.create(name='www.rostrepalid.org')
Domain.objects.create(name='mail.rostrepalid.org') Domain.objects.create(name='mail.rostrepalid.org')
self.assertEqual(2, len(domain.subdomains.all()))
def test_top_relation(self):
self.assertEqual(2, len(self.domain.subdomains.all()))
def test_render_zone(self): def test_render_zone(self):
print self.domain.render_zone() account = self.create_account()
domain = Domain.objects.create(name='rostrepalid.org', account=account)
domain.render_zone()

View file

@ -88,18 +88,16 @@ class BackendLogAdmin(admin.ModelAdmin):
) )
list_display_links = ('id', 'backend') list_display_links = ('id', 'backend')
list_filter = ('state', 'backend') list_filter = ('state', 'backend')
date_hierarchy = 'updated_at'
inlines = [BackendOperationInline] inlines = [BackendOperationInline]
fields = [ fields = [
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout', 'backend', 'server_link', 'state', 'mono_script', 'mono_stdout',
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created', 'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
'display_updated', 'execution_time' 'execution_time'
] ]
readonly_fields = fields readonly_fields = fields
server_link = admin_link('server') server_link = admin_link('server')
display_updated = admin_date('updated_at') display_created = admin_date('created_at', short_description=_("Created"))
display_created = admin_date('created_at')
display_state = admin_colored('state', colors=STATE_COLORS) display_state = admin_colored('state', colors=STATE_COLORS)
mono_script = display_mono('script') mono_script = display_mono('script')
mono_stdout = display_mono('stdout') mono_stdout = display_mono('stdout')

View file

@ -52,9 +52,10 @@ def message_user(request, logs):
_('{errors} out of {total} <a href="{url}">banckends</a> has fail to execute.'), _('{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} out of {total} <a href="{url}">banckends</a> have fail to execute.'),
errors) errors)
messages.error(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
else: else:
msg = ungettext( msg = ungettext(
_('{total} <a href="{url}">banckend</a> has been executed.'), _('{total} <a href="{url}">banckend</a> has been executed.'),
_('{total} <a href="{url}">banckends</a> have been executed.'), _('{total} <a href="{url}">banckends</a> have been executed.'),
total) total)
messages.warning(request, mark_safe(msg.format(errors=errors, total=total, url=url))) messages.success(request, mark_safe(msg.format(total=total, url=url)))

View file

@ -1,8 +1,11 @@
import socket
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_ip_address, ValidationError
from orchestra.models.fields import NullableCharField from orchestra.models.fields import NullableCharField
from orchestra.utils.apps import autodiscover from orchestra.utils.apps import autodiscover
@ -28,6 +31,16 @@ class Server(models.Model):
return self.address return self.address
return self.name return self.name
def get_ip(self):
if self.address:
return self.address
try:
validate_ip_address(self.name)
except ValidationError:
return socket.gethostbyname(self.name)
else:
return self.name
class BackendLog(models.Model): class BackendLog(models.Model):
RECEIVED = 'RECEIVED' RECEIVED = 'RECEIVED'

View file

@ -1,7 +1,6 @@
import ftplib import ftplib
import os import os
import re import re
import socket
from functools import partial from functools import partial
import paramiko import paramiko

View file

@ -159,7 +159,8 @@ function install_requirements () {
selenium \ selenium \
xvfbwrapper \ xvfbwrapper \
freezegun \ freezegun \
coverage" coverage \
orchestra-orm==dev"
fi fi
# Make sure locales are in place before installing postgres # Make sure locales are in place before installing postgres

View file

@ -29,6 +29,14 @@ def validate_ipv6_address(value):
raise ValidationError(msg) raise ValidationError(msg)
def validate_ip_address(value):
msg = _("%s is not a valid IP address") % value
try:
ip = IP(value)
except:
raise ValidationError(msg)
def validate_name(value): def validate_name(value):
""" """
A single non-empty line of free-form text with no whitespace. A single non-empty line of free-form text with no whitespace.

View file

@ -12,13 +12,3 @@ Bind9 Master and Slave
mkdir /etc/bind/master mkdir /etc/bind/master
chown bind.bind /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; };
```

View file

@ -1 +1,4 @@
MySQL
=====
apt-get install mysql-server apt-get install mysql-server