Initial implementation of backend arbitrary action execution
This commit is contained in:
parent
a58ebbd40a
commit
b93be150c2
|
@ -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__
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue