Added support for retrying backend executions
This commit is contained in:
parent
cbdac257a0
commit
f6e79fd161
5
TODO.md
5
TODO.md
|
@ -457,18 +457,13 @@ mkhomedir_helper or create ssh homes with bash.rc and such
|
||||||
* setuppostgres use porject_name for db name and user instead of orchestra
|
* setuppostgres use porject_name for db name and user instead of orchestra
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
||||||
|
|
||||||
|
|
||||||
# Mv .deleted make sure it works with nested destinations
|
# Mv .deleted make sure it works with nested destinations
|
||||||
# Re-run backends (save regenerate, delete run same script) warning on confirmation page: DELETED objects will be deleted on the server if you have recreated them.
|
|
||||||
# Automatically re-run backends until success? only timedout executions?
|
# Automatically re-run backends until success? only timedout executions?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Quick start
|
### Quick start
|
||||||
0. Install orchestra following any of these methods:
|
0. Install orchestra following any of these methods:
|
||||||
1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup)
|
1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup)
|
||||||
|
|
|
@ -49,7 +49,7 @@ class MailboxForm(forms.ModelForm):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data['name']
|
||||||
max_length = settings.MAILBOXES_NAME_MAX_LENGTH
|
max_length = settings.MAILBOXES_NAME_MAX_LENGTH
|
||||||
if len(name) > max_length:
|
if len(name) > max_length:
|
||||||
raise ValidationError("Name length should be less than %i" % max_length)
|
raise ValidationError("Name length should be less than %i." % max_length)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class MailboxCreationForm(UserCreationForm, MailboxForm):
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
# Since model.clean() will check this, this is redundant,
|
# Since model.clean() will check this, this is redundant,
|
||||||
# but it sets a nicer error message than the ORM and avoids conflicts with contrib.auth
|
# but it sets a nicer error message than the ORM and avoids conflicts with contrib.auth
|
||||||
name = self.cleaned_data["name"]
|
name = super().clean_name()
|
||||||
try:
|
try:
|
||||||
self._meta.model._default_manager.get(name=name)
|
self._meta.model._default_manager.get(name=name)
|
||||||
except self._meta.model.DoesNotExist:
|
except self._meta.model.DoesNotExist:
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
from orchestra.utils.python import AttrDict
|
||||||
|
|
||||||
from .backends import ServiceBackend, ServiceController, replace
|
from .backends import ServiceBackend, ServiceController, replace
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,3 +79,13 @@ class Operation():
|
||||||
instance=self.instance,
|
instance=self.instance,
|
||||||
action=self.action,
|
action=self.action,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, operation, log=None):
|
||||||
|
routes = None
|
||||||
|
if log:
|
||||||
|
routes = {
|
||||||
|
(operation.backend, operation.action): AttrDict(host=log.server)
|
||||||
|
}
|
||||||
|
return cls(operation.backend_class, operation.instance, operation.action, routes=routes)
|
||||||
|
|
||||||
|
|
63
orchestra/contrib/orchestration/actions.py
Normal file
63
orchestra/contrib/orchestration/actions.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.admin import helpers
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.admin.utils import get_object_from_url, change_url
|
||||||
|
from orchestra.contrib.orchestration.helpers import message_user
|
||||||
|
|
||||||
|
from . import Operation
|
||||||
|
from .models import BackendOperation
|
||||||
|
|
||||||
|
|
||||||
|
def retry_backend(modeladmin, request, queryset):
|
||||||
|
if request.POST.get('post') == 'generic_confirmation':
|
||||||
|
operations = []
|
||||||
|
for log in queryset.prefetch_related('operations__instance'):
|
||||||
|
for operation in log.operations.all():
|
||||||
|
if operation.instance:
|
||||||
|
op = Operation.load(operation)
|
||||||
|
operations.append(op)
|
||||||
|
if not operations:
|
||||||
|
messages.warning(request, _("No backend operation has been executed."))
|
||||||
|
else:
|
||||||
|
logs = Operation.execute(operations)
|
||||||
|
message_user(request, logs)
|
||||||
|
Operation.execute(operations)
|
||||||
|
return
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
display_objects = []
|
||||||
|
deleted_objects = []
|
||||||
|
related_operations = queryset.values_list('operations__id', flat=True).distinct()
|
||||||
|
related_operations = BackendOperation.objects.filter(pk__in=related_operations)
|
||||||
|
for op in related_operations.select_related('log__server').prefetch_related('instance'):
|
||||||
|
if not op.instance:
|
||||||
|
deleted_objects.append(op)
|
||||||
|
else:
|
||||||
|
context = {
|
||||||
|
'backend': op.log.backend,
|
||||||
|
'action': op.action,
|
||||||
|
'instance': op.instance,
|
||||||
|
'instance_url': change_url(op.instance),
|
||||||
|
'server': op.log.server,
|
||||||
|
'server_url': change_url(op.log.server),
|
||||||
|
}
|
||||||
|
display_objects.append(mark_safe(
|
||||||
|
'%(backend)s.%(action)s(<a href="%(instance_url)s">%(instance)s</a>) @ <a href="%(server_url)s">%(server)s</a>' % context
|
||||||
|
))
|
||||||
|
context = {
|
||||||
|
'title': _("Are you sure to execute the following backends?"),
|
||||||
|
'action_name': _('Retry backend'),
|
||||||
|
'action_value': 'retry_backend',
|
||||||
|
'display_objects': display_objects,
|
||||||
|
'deleted_objects': deleted_objects,
|
||||||
|
'queryset': queryset,
|
||||||
|
'opts': opts,
|
||||||
|
'app_label': opts.app_label,
|
||||||
|
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
'obj': get_object_from_url(modeladmin, request),
|
||||||
|
}
|
||||||
|
return render(request, 'admin/orchestration/backends/retry.html', context)
|
||||||
|
retry_backend.short_description = _("Retry")
|
||||||
|
retry_backend.url_name = 'retry'
|
|
@ -3,11 +3,12 @@ from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin, ChangeViewActionsMixin
|
||||||
from orchestra.admin.utils import admin_link, admin_date, admin_colored, display_mono, display_code
|
from orchestra.admin.utils import admin_link, admin_date, admin_colored, display_mono, display_code
|
||||||
from orchestra.plugins.admin import display_plugin_field
|
from orchestra.plugins.admin import display_plugin_field
|
||||||
|
|
||||||
from . import settings, helpers
|
from . import settings, helpers
|
||||||
|
from .actions import retry_backend
|
||||||
from .backends import ServiceBackend
|
from .backends import ServiceBackend
|
||||||
from .forms import RouteForm
|
from .forms import RouteForm
|
||||||
from .models import Server, Route, BackendLog, BackendOperation
|
from .models import Server, Route, BackendLog, BackendOperation
|
||||||
|
@ -128,7 +129,7 @@ class BackendOperationInline(admin.TabularInline):
|
||||||
return queryset.prefetch_related('instance')
|
return queryset.prefetch_related('instance')
|
||||||
|
|
||||||
|
|
||||||
class BackendLogAdmin(admin.ModelAdmin):
|
class BackendLogAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id', 'backend', 'server_link', 'display_state', 'exit_code',
|
'id', 'backend', 'server_link', 'display_state', 'exit_code',
|
||||||
'display_created', 'execution_time',
|
'display_created', 'execution_time',
|
||||||
|
@ -144,6 +145,8 @@ class BackendLogAdmin(admin.ModelAdmin):
|
||||||
'execution_time'
|
'execution_time'
|
||||||
)
|
)
|
||||||
readonly_fields = fields
|
readonly_fields = fields
|
||||||
|
actions = (retry_backend,)
|
||||||
|
change_view_actions = actions
|
||||||
|
|
||||||
server_link = admin_link('server')
|
server_link = admin_link('server')
|
||||||
display_created = admin_date('created_at', short_description=_("Created"))
|
display_created = admin_date('created_at', short_description=_("Created"))
|
||||||
|
|
|
@ -149,9 +149,14 @@ def SSH(*args, **kwargs):
|
||||||
|
|
||||||
def Python(backend, log, server, cmds, async=False):
|
def Python(backend, log, server, cmds, async=False):
|
||||||
script = ''
|
script = ''
|
||||||
|
functions = set()
|
||||||
|
for cmd in cmds:
|
||||||
|
if cmd.func not in functions:
|
||||||
|
functions.add(cmd.func)
|
||||||
|
script += textwrap.dedent(''.join(inspect.getsourcelines(cmd.func)[0]))
|
||||||
|
script += '\n'
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
script += '# %s %s\n' % (cmd.func.__name__, cmd.args)
|
script += '# %s %s\n' % (cmd.func.__name__, cmd.args)
|
||||||
script += textwrap.dedent(''.join(inspect.getsourcelines(cmd.func)[0]))
|
|
||||||
log.state = log.STARTED
|
log.state = log.STARTED
|
||||||
log.script = '\n'.join((log.script, script))
|
log.script = '\n'.join((log.script, script))
|
||||||
log.save(update_fields=('script', 'state', 'updated_at'))
|
log.save(update_fields=('script', 'state', 'updated_at'))
|
||||||
|
|
|
@ -150,7 +150,7 @@ class BackendOperation(models.Model):
|
||||||
verbose_name_plural = _("Operations")
|
verbose_name_plural = _("Operations")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
|
return '%s.%s(%s)' % (self.backend, self.action, self.instance or self.instance_repr)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def backend_class(self):
|
def backend_class(self):
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "admin/orchestra/generic_confirmation.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
{% if deleted_objects %}
|
||||||
|
<h4>The following operations refere to deleted objects and will not be executed</h4>
|
||||||
|
<ul>{{ deleted_objects | unordered_list }}</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -53,7 +53,7 @@ def set_permission(modeladmin, request, queryset):
|
||||||
msg = _("%(action)s %(perms)s permission to %(to)s") % context
|
msg = _("%(action)s %(perms)s permission to %(to)s") % context
|
||||||
modeladmin.log_change(request, user, msg)
|
modeladmin.log_change(request, user, msg)
|
||||||
if not operations:
|
if not operations:
|
||||||
messages.error(request, "No backend operation has been executed.")
|
messages.error(request, _("No backend operation has been executed."))
|
||||||
else:
|
else:
|
||||||
logs = Operation.execute(operations)
|
logs = Operation.execute(operations)
|
||||||
helpers.message_user(request, logs)
|
helpers.message_user(request, logs)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
|
from orchestra.utils.python import OrderedSet
|
||||||
|
|
||||||
from .. import settings, utils
|
from .. import settings, utils
|
||||||
from ..options import AppOption
|
from ..options import AppOption
|
||||||
|
@ -89,12 +90,13 @@ class PHPApp(AppType):
|
||||||
init_vars[name] = value
|
init_vars[name] = value
|
||||||
# Disable functions
|
# Disable functions
|
||||||
if self.PHP_DISABLED_FUNCTIONS:
|
if self.PHP_DISABLED_FUNCTIONS:
|
||||||
enable_functions = init_vars.pop('enable_functions', '')
|
enable_functions = init_vars.pop('enable_functions', None)
|
||||||
disable_functions = set(init_vars.pop('disable_functions', '').split(','))
|
enable_functions = OrderedSet(enable_functions.split(',') if enable_functions else ())
|
||||||
|
disable_functions = init_vars.pop('disable_functions', None)
|
||||||
|
disable_functions = OrderedSet(disable_functions.split(',') if disable_functions else ())
|
||||||
if disable_functions or enable_functions or self.is_fpm:
|
if disable_functions or enable_functions or self.is_fpm:
|
||||||
# FPM: Defining 'disable_functions' or 'disable_classes' will not overwrite previously
|
# FPM: Defining 'disable_functions' or 'disable_classes' will not overwrite previously
|
||||||
# defined php.ini values, but will append the new value
|
# defined php.ini values, but will append the new value
|
||||||
enable_functions = set(enable_functions.split(','))
|
|
||||||
for function in self.PHP_DISABLED_FUNCTIONS:
|
for function in self.PHP_DISABLED_FUNCTIONS:
|
||||||
if function not in enable_functions:
|
if function not in enable_functions:
|
||||||
disable_functions.add(function)
|
disable_functions.add(function)
|
||||||
|
@ -119,6 +121,7 @@ class PHPApp(AppType):
|
||||||
init_vars['post_max_size'] = post_max_size
|
init_vars['post_max_size'] = post_max_size
|
||||||
if upload_max_filesize_value > post_max_size_value:
|
if upload_max_filesize_value > post_max_size_value:
|
||||||
init_vars['post_max_size'] = upload_max_filesize
|
init_vars['post_max_size'] = upload_max_filesize
|
||||||
|
print(init_vars)
|
||||||
return init_vars
|
return init_vars
|
||||||
|
|
||||||
def get_directive_context(self):
|
def get_directive_context(self):
|
||||||
|
|
Loading…
Reference in a new issue