From 6fa29a15b8878d4ce88377e8f3f8293bf9d9fe96 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Sat, 9 May 2015 11:41:52 +0000 Subject: [PATCH] Clone database connection on manager.execute --- TODO.md | 2 ++ orchestra/contrib/orchestration/backends.py | 6 +++- orchestra/contrib/orchestration/manager.py | 15 ++++------ orchestra/contrib/systemusers/actions.py | 2 ++ orchestra/contrib/systemusers/backends.py | 2 +- .../systemuser/grant_permission.html | 2 +- orchestra/utils/db.py | 29 +++++++++++++++++++ 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 403fd06c..3312404f 100644 --- a/TODO.md +++ b/TODO.md @@ -355,3 +355,5 @@ make django admin taskstate uncollapse fucking traceback, ( if exists ?) # backend.context and backned.instance provided when an action is called? like forms.cleaned_data: do it on manager.generation(backend.context = backend.get_context()) or in backend.__getattr__ ? also backend.head,tail,content switching on manager.generate()? # replace return_code by exit_code everywhere + +# plan.rate registry diff --git a/orchestra/contrib/orchestration/backends.py b/orchestra/contrib/orchestration/backends.py index cd52aa79..051ae135 100644 --- a/orchestra/contrib/orchestration/backends.py +++ b/orchestra/contrib/orchestration/backends.py @@ -167,7 +167,11 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount): run = bool(self.scripts) or (self.force_empty_action_execution or bool(self.content)) if not run: state = BackendLog.NOTHING - log = BackendLog.objects.create(backend=self.get_name(), state=state, server=server) + using = kwargs.pop('using', None) + manager = BackendLog.objects + if using: + manager = manager.using(using) + log = manager.create(backend=self.get_name(), state=state, server=server) return log def execute(self, server, async=False, log=None): diff --git a/orchestra/contrib/orchestration/manager.py b/orchestra/contrib/orchestration/manager.py index c46de751..92506be2 100644 --- a/orchestra/contrib/orchestration/manager.py +++ b/orchestra/contrib/orchestration/manager.py @@ -6,7 +6,7 @@ from functools import partial from django.core.mail import mail_admins -from orchestra.utils.db import close_connection +from orchestra.utils import db from orchestra.utils.python import import_class, OrderedSet from . import settings, Operation @@ -31,7 +31,7 @@ def keep_log(execute, log, operations): send_report(execute, args, log) except Exception as e: trace = traceback.format_exc() - log.state = BackendLog.EXCEPTION + log.state = log.EXCEPTION log.stderr = trace log.save() subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs)) @@ -122,12 +122,9 @@ def execute(scripts, serialize=False, async=None): kwargs = { 'async': async, } - log = backend.create_log(*args, **kwargs) - # TODO Perform this shit outside of the current transaction in a non-hacky way - #t = threading.Thread(target=backend.create_log, args=args, kwargs=kwargs) - #t.start() - #log = t.join() - # End of hack + with db.clone(model=BackendLog) as handle: + log = backend.create_log(*args, using=handle.target) + log._state.db = handle.origin kwargs['log'] = log task = keep_log(backend.execute, log, operations) logger.debug('%s is going to be executed on %s.' % (backend, route.host)) @@ -135,7 +132,7 @@ def execute(scripts, serialize=False, async=None): # Execute one backend at a time, no need for threads task(*args, **kwargs) else: - task = close_connection(task) + task = db.close_connection(task) thread = threading.Thread(target=task, args=args, kwargs=kwargs) thread.start() if not async: diff --git a/orchestra/contrib/systemusers/actions.py b/orchestra/contrib/systemusers/actions.py index 208a05bb..5bcfa37c 100644 --- a/orchestra/contrib/systemusers/actions.py +++ b/orchestra/contrib/systemusers/actions.py @@ -28,6 +28,7 @@ def grant_permission(modeladmin, request, queryset): for user in queryset: user.grant_to = to user.grant_ro = ro + # DOn't collect, execute right away for path validation OperationsMiddleware.collect('grant_permission', instance=user) context = { 'type': _("read-only") if ro else _("read-write"), @@ -35,6 +36,7 @@ def grant_permission(modeladmin, request, queryset): } msg = _("Granted %(type)s permissions on %(to)s") % context modeladmin.log_change(request, user, msg) + # TODO feedback message return opts = modeladmin.model._meta app_label = opts.app_label diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py index 3603a8ad..a831a418 100644 --- a/orchestra/contrib/systemusers/backends.py +++ b/orchestra/contrib/systemusers/backends.py @@ -72,7 +72,7 @@ class UNIXUserBackend(ServiceController): 'to': user.grant_to, 'ro': user.grant_ro, }) - if user.ro: + if user.grant_ro: self.append('echo "acl add read permissions for %(user)s to %(to)s"' % context) else: self.append('echo "acl add read-write permissions for %(user)s to %(to)s"' % context) diff --git a/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/grant_permission.html b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/grant_permission.html index 26063212..3fa452c3 100644 --- a/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/grant_permission.html +++ b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/grant_permission.html @@ -37,7 +37,7 @@
{{ form.path_extension.errors }} - {{ form.base_path }} + {{ form.base_path }}{% for x in ""|ljust:"50" %} {% endfor %}

{{ form.base_path.help_text|safe }}

diff --git a/orchestra/utils/db.py b/orchestra/utils/db.py index 679f369e..254ea046 100644 --- a/orchestra/utils/db.py +++ b/orchestra/utils/db.py @@ -1,6 +1,7 @@ import sys from django import db +from django.conf import settings as djsettings def running_syncdb(): @@ -31,3 +32,31 @@ def close_connection(execute): finally: db.connection.close() return wrapper + + +class clone(object): + """ + clone database in order to have fresh connections and make queries outside the current transaction + + with db.clone(model=BackendLog) as handle: + log = BackendLog.objects.using(handle.target).create() + log._state.db = handle.origin + + """ + def __init__(self, model=None, origin='', target=''): + if model is not None: + origin = db.router.db_for_write(model) + self.origin = origin or db.DEFAULT_DB_ALIAS + self.target = target or 'other_' + origin + + def __enter__(self): + djsettings.DATABASES[self.target] = djsettings.DATABASES[self.origin] + # Because db.connections.datases is a cached property + self.old_connections = db.connections + db.connections = db.utils.ConnectionHandler() + return self + + def __exit__(self, type, value, traceback): + db.connections[self.target].close() + djsettings.DATABASES.pop(self.target) + db.connections = self.old_connections