diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py index 9851009b..5fb7fb06 100644 --- a/orchestra/contrib/domains/backends.py +++ b/orchestra/contrib/domains/backends.py @@ -17,6 +17,7 @@ class Bind9MasterDomainBackend(ServiceController): ('domains.Domain', 'origin'), ) ignore_fields = ['serial'] + CONF_PATH = settings.DOMAINS_MASTERS_PATH @classmethod def is_main(cls, obj): @@ -27,6 +28,10 @@ class Bind9MasterDomainBackend(ServiceController): def save(self, domain): context = self.get_context(domain) domain.refresh_serial() + self.update_zone(domain, context) + self.update_conf(context) + + def update_zone(self, domain, context): context['zone'] = ';; %(banner)s\n' % context context['zone'] += domain.render_zone().replace("'", '"') self.append(textwrap.dedent("""\ @@ -37,7 +42,6 @@ class Bind9MasterDomainBackend(ServiceController): mv %(zone_path)s.tmp %(zone_path)s """) % context ) - self.update_conf(context) def update_conf(self, context): self.append(textwrap.dedent("""\ @@ -88,7 +92,9 @@ class Bind9MasterDomainBackend(ServiceController): return servers def get_slaves(self, domain): - return self.get_servers(domain, Bind9SlaveDomainBackend) + return set(settings.DOMAINS_SLAVES).union( + set(self.get_servers(domain, Bind9SlaveDomainBackend)) + ) def get_context(self, domain): slaves = self.get_slaves(domain) @@ -99,7 +105,7 @@ class Bind9MasterDomainBackend(ServiceController): 'banner': self.get_banner(), 'slaves': '; '.join(slaves) or 'none', 'also_notify': '; '.join(slaves) + ';' if slaves else '', - 'conf_path': settings.DOMAINS_MASTERS_PATH, + 'conf_path': self.CONF_PATH, } context['conf'] = textwrap.dedent(""" zone "%(name)s" { @@ -118,6 +124,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): related_models = ( ('domains.Domain', 'origin'), ) + CONF_PATH = settings.DOMAINS_SLAVES_PATH def save(self, domain): context = self.get_context(domain) @@ -132,7 +139,9 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi') def get_masters(self, domain): - return self.get_servers(domain, Bind9MasterDomainBackend) + return set(settings.DOMAINS_MASTERS).union( + set(self.get_servers(domain, Bind9MasterDomainBackend)) + ) def get_context(self, domain): context = { @@ -140,7 +149,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): 'banner': self.get_banner(), 'subdomains': domain.subdomains.all(), 'masters': '; '.join(self.get_masters(domain)) or 'none', - 'conf_path': settings.DOMAINS_SLAVES_PATH, + 'conf_path': self.CONF_PATH, } context['conf'] = textwrap.dedent(""" zone "%(name)s" { diff --git a/orchestra/contrib/domains/settings.py b/orchestra/contrib/domains/settings.py index 6ce7037d..5f6960fa 100644 --- a/orchestra/contrib/domains/settings.py +++ b/orchestra/contrib/domains/settings.py @@ -90,3 +90,15 @@ DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN', # '%(site_dir)s/forbidden_domains.list') '' ) + + +DOMAINS_MASTERS = getattr(settings, 'DOMAINS_MASTERS', + # Additional master server ip addresses other than autodiscovered by router.get_servers() + () +) + + +DOMAINS_SLAVES = getattr(settings, 'DOMAINS_SLAVES', + # Additional slave server ip addresses other than autodiscovered by router.get_servers() + () +) diff --git a/orchestra/contrib/orchestration/management/commands/orchestrate.py b/orchestra/contrib/orchestration/management/commands/orchestrate.py index c7afb3f0..9ee38daf 100644 --- a/orchestra/contrib/orchestration/management/commands/orchestrate.py +++ b/orchestra/contrib/orchestration/management/commands/orchestrate.py @@ -1,9 +1,12 @@ import sys -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError from django.db.models.loading import get_model -from orchestra.contrib.orchestration import manager +from orchestra.contrib.orchestration import manager, Operation +from orchestra.contrib.orchestration.models import Server +from orchestra.contrib.orchestration.backends import ServiceBackend +from orchestra.utils.python import import_class class Command(BaseCommand): @@ -18,13 +21,28 @@ class Command(BaseCommand): help='Tells Django to NOT prompt the user for input of any kind.') parser.add_argument('--action', action='store', dest='action', default='save', help='Executes action. Defaults to "save".') + parser.add_argument('--servers', action='store', dest='servers', + default='save', help='Overrides route server resolution with the provided server.') + parser.add_argument('--backends', action='store', dest='backends', + default='save', help='Overrides backend.') + parser.add_argument('--listbackends', action='store_true', dest='list_backends', default=False, + help='List available baclends.') parser.add_argument('--dry-run', action='store_true', dest='dry', default=False, help='Only prints scrtipt.') def handle(self, *args, **options): + list_backends = options.get('list_backends') + if list_backends: + for backend in ServiceBackend.get_backends(): + print(str(backend).split("'")[1]) + return model = get_model(*options['model'].split('.')) action = options.get('action') interactive = options.get('interactive') + servers = options.get('servers', '').split(',') + backends = options.get('backends', '').split(',') + if (servers and not backends) or (not servers and backends): + raise CommandError("--backends and --servers go in tandem.") dry = options.get('dry') kwargs = {} for comp in options.get('query', []): @@ -34,8 +52,23 @@ class Command(BaseCommand): operations = [] operations = set() route_cache = {} - for instance in model.objects.filter(**kwargs): - manager.collect(instance, action, operations=operations, route_cache=route_cache) + if servers: + server_objects = [] + # Get and create missing Servers + for server in servers: + try: + server = Server.objects.get(address=server) + except Server.DoesNotExist: + server = Server.objects.create(name=server, address=server) + server_objects.append(server) + # Generate operations for the given backend + for instance in model.objects.filter(**kwargs): + for backend in backends: + backend = import_class(backend) + operations.add(Operation(backend, instance, action, servers=server_objects)) + else: + for instance in model.objects.filter(**kwargs): + manager.collect(instance, action, operations=operations, route_cache=route_cache) scripts, block = manager.generate(operations) servers = [] # Print scripts @@ -64,7 +97,7 @@ class Command(BaseCommand): if not dry: logs = manager.execute(scripts, block=block) for log in logs: - print(log.stdout) - sys.stderr.write(log.stderr) + print(log.stdout.encode('utf8', errors='replace')) + sys.stderr.write(log.stderr.encode('utf8', errors='replace')) for log in logs: print(log.backend, log.state) diff --git a/orchestra/contrib/orchestration/methods.py b/orchestra/contrib/orchestration/methods.py index 7cbfc378..c0340e4e 100644 --- a/orchestra/contrib/orchestration/methods.py +++ b/orchestra/contrib/orchestration/methods.py @@ -120,6 +120,9 @@ def SSH(backend, log, server, cmds, async=False): logger.debug(log.traceback) log.save() finally: + if log.state == log.STARTED: + log.state = log.ABORTED + log.save(update_fields=['state']) if channel is not None: channel.close() if ssh is not None: diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py index 806c138c..679909a7 100644 --- a/orchestra/contrib/orchestration/models.py +++ b/orchestra/contrib/orchestration/models.py @@ -52,6 +52,7 @@ class BackendLog(models.Model): FAILURE = 'FAILURE' ERROR = 'ERROR' REVOKED = 'REVOKED' + ABORTED = 'ABORTED' # Special state for mocked backendlogs EXCEPTION = 'EXCEPTION' @@ -62,6 +63,7 @@ class BackendLog(models.Model): (SUCCESS, SUCCESS), (FAILURE, FAILURE), (ERROR, ERROR), + (ABORTED, ABORTED), (REVOKED, REVOKED), )