Initial implementation of backend arbitrary action execution

This commit is contained in:
Marc Aymerich 2014-11-14 16:52:54 +00:00
parent a58ebbd40a
commit b93be150c2
6 changed files with 41 additions and 12 deletions

View file

@ -64,8 +64,24 @@ class ServiceBackend(plugins.Plugin):
return None return None
@classmethod @classmethod
def get_backends(cls): def get_backends(cls, instance=None, action=None):
return cls.get_plugins() backends = cls.get_plugins()
included = []
# Filter for instance or action
if instance or action:
for backend in backends:
include = True
if instance:
opts = instance._meta
if backend.model != '.'.join((opts.app_label, opts.object_name)):
include = False
if include and action:
if action not in backend.get_actions():
include = False
if include:
included.append(backend)
backends = included
return backends
@classmethod @classmethod
def get_backend(cls, name): def get_backend(cls, name):
@ -126,6 +142,7 @@ class ServiceController(ServiceBackend):
@classmethod @classmethod
def get_backends(cls): def get_backends(cls):
""" filter controller classes """ """ filter controller classes """
backends = super(ServiceController, cls).get_backends()
return [ return [
plugin for plugin in cls.plugins if ServiceController in plugin.__mro__ backend for backend in backends if ServiceController in backend.__mro__
] ]

View file

@ -19,8 +19,6 @@ transports = {}
def BashSSH(backend, log, server, cmds): def BashSSH(backend, log, server, cmds):
from .models import BackendLog
script = '\n'.join(['set -e', 'set -o pipefail'] + cmds + ['exit 0']) script = '\n'.join(['set -e', 'set -o pipefail'] + cmds + ['exit 0'])
script = script.replace('\r', '') script = script.replace('\r', '')
digest = hashlib.md5(script).hexdigest() digest = hashlib.md5(script).hexdigest()
@ -48,7 +46,7 @@ def BashSSH(backend, log, server, cmds):
ssh.connect(addr, username='root', key_filename=settings.ORCHESTRATION_SSH_KEY_PATH, timeout=10) ssh.connect(addr, username='root', key_filename=settings.ORCHESTRATION_SSH_KEY_PATH, timeout=10)
except socket.error: except socket.error:
logger.error('%s timed out on %s' % (backend, server)) logger.error('%s timed out on %s' % (backend, server))
log.state = BackendLog.TIMEOUT log.state = log.TIMEOUT
log.save(update_fields=['state']) log.save(update_fields=['state'])
return return
transport = ssh.get_transport() transport = ssh.get_transport()
@ -92,11 +90,11 @@ def BashSSH(backend, log, server, cmds):
if channel.exit_status_ready(): if channel.exit_status_ready():
break break
log.exit_code = exit_code = channel.recv_exit_status() log.exit_code = exit_code = channel.recv_exit_status()
log.state = BackendLog.SUCCESS if exit_code == 0 else BackendLog.FAILURE log.state = log.SUCCESS if exit_code == 0 else log.FAILURE
logger.debug('%s execution state on %s is %s' % (backend, server, log.state)) logger.debug('%s execution state on %s is %s' % (backend, server, log.state))
log.save() log.save()
except: except:
log.state = BackendLog.ERROR log.state = log.ERROR
log.traceback = ExceptionInfo(sys.exc_info()).traceback log.traceback = ExceptionInfo(sys.exc_info()).traceback
logger.error('Exception while executing %s on %s' % (backend, server)) logger.error('Exception while executing %s on %s' % (backend, server))
logger.debug(log.traceback) logger.debug(log.traceback)
@ -109,7 +107,6 @@ def BashSSH(backend, log, server, cmds):
def Python(backend, log, server, cmds): def Python(backend, log, server, cmds):
from .models import BackendLog
script = [ str(cmd.func.func_name) + str(cmd.args) for cmd in cmds ] script = [ str(cmd.func.func_name) + str(cmd.args) for cmd in cmds ]
script = json.dumps(script, indent=4).replace('"', '') script = json.dumps(script, indent=4).replace('"', '')
log.script = '\n'.join([log.script, script]) log.script = '\n'.join([log.script, script])
@ -121,10 +118,10 @@ def Python(backend, log, server, cmds):
stdout += str(result) stdout += str(result)
except: except:
log.exit_code = 1 log.exit_code = 1
log.state = BackendLog.FAILURE log.state = log.FAILURE
log.traceback = ExceptionInfo(sys.exc_info()).traceback log.traceback = ExceptionInfo(sys.exc_info()).traceback
else: else:
log.exit_code = 0 log.exit_code = 0
log.state = BackendLog.SUCCESS log.state = log.SUCCESS
log.stdout += stdout log.stdout += stdout
log.save() log.save()

View file

@ -50,6 +50,7 @@ class OperationsMiddleware(object):
return return
pending_operations = cls.get_pending_operations() pending_operations = cls.get_pending_operations()
for backend in ServiceBackend.get_backends(): for backend in ServiceBackend.get_backends():
# Check if there exists a related instance to be executed for this backend
instance = None instance = None
if backend.is_main(kwargs['instance']): if backend.is_main(kwargs['instance']):
instance = kwargs['instance'] instance = kwargs['instance']
@ -61,6 +62,7 @@ class OperationsMiddleware(object):
instance = candidate instance = candidate
# related objects with backend.model trigger save() # related objects with backend.model trigger save()
action = Operation.SAVE action = Operation.SAVE
# Maintain consistent state of pending_operations based on save/delete behaviour
if instance is not None: if instance is not None:
# Prevent creating a deleted instance by deleting existing saves # Prevent creating a deleted instance by deleting existing saves
if action == Operation.DELETE: if action == Operation.DELETE:

View file

@ -138,6 +138,12 @@ class BackendOperation(models.Model):
def execute(cls, operations): def execute(cls, operations):
return manager.execute(operations) return manager.execute(operations)
@classmethod
def execute_action(cls, instance, action):
backends = ServiceBackend.get_backends(instance=instance, action=action)
operations = [cls.create(backend, instance, action) for backend in backends]
return cls.execute(operations)
def backend_class(self): def backend_class(self):
return ServiceBackend.get_backend(self.backend) return ServiceBackend.get_backend(self.backend)

View file

@ -10,6 +10,7 @@ from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.admin.decorators import action_with_confirmation from orchestra.admin.decorators import action_with_confirmation
from orchestra.admin.utils import change_url from orchestra.admin.utils import change_url
from orchestra.apps.orchestration.models import BackendOperation as Operation
class GrantPermissionForm(forms.Form): class GrantPermissionForm(forms.Form):
@ -22,7 +23,8 @@ class GrantPermissionForm(forms.Form):
@action_with_confirmation(extra_context=dict(form=GrantPermissionForm())) @action_with_confirmation(extra_context=dict(form=GrantPermissionForm()))
def grant_permission(modeladmin, request, queryset): def grant_permission(modeladmin, request, queryset):
user = queryset.get()
log = Operation.execute_action(user, 'grant_permission')
# TODO # TODO
pass
grant_permission.url_name = 'grant-permission' grant_permission.url_name = 'grant-permission'
grant_permission.verbose_name = _("Grant permission") grant_permission.verbose_name = _("Grant permission")

View file

@ -12,6 +12,7 @@ from . import settings
class SystemUserBackend(ServiceController): class SystemUserBackend(ServiceController):
verbose_name = _("System user") verbose_name = _("System user")
model = 'systemusers.SystemUser' model = 'systemusers.SystemUser'
actions = ('save', 'delete', 'grant_permission')
def save(self, user): def save(self, user):
context = self.get_context(user) context = self.get_context(user)
@ -40,6 +41,10 @@ class SystemUserBackend(ServiceController):
self.append("groupdel %(username)s || true" % context) self.append("groupdel %(username)s || true" % context)
self.delete_home(context, user) self.delete_home(context, user)
def grant_permission(self, user):
context = self.get_context(user)
# TODO setacl
def delete_home(self, context, user): def delete_home(self, context, user):
if user.is_main: if user.is_main:
# TODO delete instead of this shit # TODO delete instead of this shit