django-orchestra/orchestra/apps/orchestration/models.py

242 lines
8.2 KiB
Python
Raw Normal View History

2014-10-15 12:47:28 +00:00
import copy
import socket
2014-05-08 16:59:35 +00:00
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
2014-10-07 13:08:59 +00:00
from django.utils.module_loading import autodiscover_modules
2014-05-08 16:59:35 +00:00
from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_ip_address, ValidationError
2014-07-10 15:19:06 +00:00
from orchestra.models.fields import NullableCharField
2014-10-07 13:08:59 +00:00
#from orchestra.utils.apps import autodiscover
2014-05-08 16:59:35 +00:00
2015-03-04 21:06:16 +00:00
from . import settings
2014-05-08 16:59:35 +00:00
from .backends import ServiceBackend
class Server(models.Model):
""" Machine runing daemons (services) """
name = models.CharField(_("name"), max_length=256, unique=True)
2014-07-10 15:19:06 +00:00
address = NullableCharField(_("address"), max_length=256, blank=True,
null=True, unique=True, help_text=_("IP address or domain name"))
2014-05-08 16:59:35 +00:00
description = models.TextField(_("description"), blank=True)
os = models.CharField(_("operative system"), max_length=32,
choices=settings.ORCHESTRATION_OS_CHOICES,
default=settings.ORCHESTRATION_DEFAULT_OS)
def __unicode__(self):
return self.name
def get_address(self):
if self.address:
return self.address
return self.name
def get_ip(self):
if self.address:
return self.address
try:
validate_ip_address(self.name)
except ValidationError:
return socket.gethostbyname(self.name)
else:
return self.name
2014-05-08 16:59:35 +00:00
class BackendLog(models.Model):
RECEIVED = 'RECEIVED'
TIMEOUT = 'TIMEOUT'
STARTED = 'STARTED'
SUCCESS = 'SUCCESS'
FAILURE = 'FAILURE'
ERROR = 'ERROR'
REVOKED = 'REVOKED'
# Special state for mocked backendlogs
EXCEPTION = 'EXCEPTION'
2014-05-08 16:59:35 +00:00
STATES = (
(RECEIVED, RECEIVED),
(TIMEOUT, TIMEOUT),
(STARTED, STARTED),
(SUCCESS, SUCCESS),
(FAILURE, FAILURE),
(ERROR, ERROR),
(REVOKED, REVOKED),
)
backend = models.CharField(_("backend"), max_length=256)
state = models.CharField(_("state"), max_length=16, choices=STATES,
default=RECEIVED)
server = models.ForeignKey(Server, verbose_name=_("server"),
related_name='execution_logs')
script = models.TextField(_("script"))
2014-07-17 16:09:24 +00:00
stdout = models.TextField(_("stdout"))
stderr = models.TextField(_("stdin"))
2014-05-08 16:59:35 +00:00
traceback = models.TextField(_("traceback"))
exit_code = models.IntegerField(_("exit code"), null=True)
task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True,
2014-07-25 15:17:50 +00:00
help_text="Celery task ID when used as execution backend")
2014-09-26 15:05:20 +00:00
created_at = models.DateTimeField(_("created"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated"), auto_now=True)
2014-05-08 16:59:35 +00:00
class Meta:
2014-09-24 20:09:41 +00:00
get_latest_by = 'id'
2014-05-08 16:59:35 +00:00
2014-11-16 18:39:31 +00:00
def __unicode__(self):
return "%s@%s" % (self.backend, self.server)
2014-05-08 16:59:35 +00:00
@property
def execution_time(self):
2014-09-30 14:46:29 +00:00
return (self.updated_at-self.created_at).total_seconds()
2014-09-10 16:53:09 +00:00
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
2014-05-08 16:59:35 +00:00
class BackendOperation(models.Model):
"""
Encapsulates an operation, storing its related object, the action and the backend.
"""
DELETE = 'delete'
2014-07-09 16:17:43 +00:00
SAVE = 'save'
MONITOR = 'monitor'
EXCEEDED = 'exceeded'
RECOVERY = 'recovery'
2014-05-08 16:59:35 +00:00
log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
2014-07-10 15:19:06 +00:00
backend = models.CharField(_("backend"), max_length=256)
2014-07-09 16:17:43 +00:00
action = models.CharField(_("action"), max_length=64)
2014-05-08 16:59:35 +00:00
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
2014-09-26 15:05:20 +00:00
2014-10-10 17:17:20 +00:00
instance = generic.GenericForeignKey('content_type', 'object_id')
2014-05-08 16:59:35 +00:00
class Meta:
verbose_name = _("Operation")
verbose_name_plural = _("Operations")
def __unicode__(self):
2014-10-10 17:17:20 +00:00
return '%s.%s(%s)' % (self.backend, self.action, self.instance)
2014-05-08 16:59:35 +00:00
def __hash__(self):
""" set() """
backend = getattr(self, 'backend', self.backend)
return hash(backend) + hash(self.instance) + hash(self.action)
2014-05-08 16:59:35 +00:00
def __eq__(self, operation):
""" set() """
return hash(self) == hash(operation)
@classmethod
def create(cls, backend, instance, action, servers=None):
op = cls(backend=backend.get_name(), instance=instance, action=action)
op.backend = backend
2014-10-15 12:47:28 +00:00
# instance should maintain any dynamic attribute until backend execution
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
op.instance = copy.deepcopy(instance)
2015-03-04 21:06:16 +00:00
op.servers = servers
2014-05-08 16:59:35 +00:00
return op
@classmethod
def execute(cls, operations, async=False):
2015-03-04 21:06:16 +00:00
from . import manager
return manager.execute(operations, async=async)
2014-07-10 15:19:06 +00:00
@classmethod
def execute_action(cls, instance, action):
backends = ServiceBackend.get_backends(instance=instance, action=action)
2015-03-04 21:06:16 +00:00
operations = [cls.create(backend_cls, instance, action) for backend_cls in backends]
return cls.execute(operations)
2015-03-04 21:06:16 +00:00
def preload_context(self):
"""
Heuristic
Running get_context will prevent most of related objects do not exist errors
"""
if self.action == self.DELETE:
if hasattr(self.backend, 'get_context'):
self.backend().get_context(self.instance)
2015-03-04 21:06:16 +00:00
2014-07-10 15:19:06 +00:00
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
2014-05-08 16:59:35 +00:00
2014-10-07 13:08:59 +00:00
autodiscover_modules('backends')
2014-05-08 16:59:35 +00:00
class Route(models.Model):
"""
Defines the routing that determine in which server a backend is executed
"""
backend = models.CharField(_("backend"), max_length=256,
2014-07-21 12:20:04 +00:00
choices=ServiceBackend.get_plugin_choices())
2014-05-08 16:59:35 +00:00
host = models.ForeignKey(Server, verbose_name=_("host"))
match = models.CharField(_("match"), max_length=256, blank=True, default='True',
help_text=_("Python expression used for selecting the targe host, "
"<em>instance</em> referes to the current object."))
# async = models.BooleanField(default=False)
# method = models.CharField(_("method"), max_lenght=32, choices=method_choices,
# default=MethodBackend.get_default())
2014-09-30 10:20:11 +00:00
is_active = models.BooleanField(_("active"), default=True)
2014-05-08 16:59:35 +00:00
class Meta:
unique_together = ('backend', 'host')
def __unicode__(self):
return "%s@%s" % (self.backend, self.host)
# def clean(self):
# backend, method = self.get_backend_class(), self.get_method_class()
# if not backend.type in method.types:
# msg = _("%s backend is not compatible with %s method")
# raise ValidationError(msg % (self.backend, self.method)
2014-07-10 10:03:22 +00:00
@classmethod
2014-07-17 16:09:24 +00:00
def get_servers(cls, operation, **kwargs):
cache = kwargs.get('cache', {})
2014-07-09 16:17:43 +00:00
servers = []
backend_cls = operation.backend
2015-03-04 21:06:16 +00:00
key = (backend_cls.get_name(), operation.action)
2014-05-08 16:59:35 +00:00
try:
2014-07-17 16:09:24 +00:00
routes = cache[key]
2014-07-10 10:03:22 +00:00
except KeyError:
2014-07-17 16:09:24 +00:00
cache[key] = []
2015-03-04 21:06:16 +00:00
for route in cls.objects.filter(is_active=True, backend=backend_cls.get_name()):
for action in backend_cls.get_actions():
2014-07-17 16:09:24 +00:00
_key = (route.backend, action)
2014-10-04 09:29:18 +00:00
try:
cache[_key].append(route)
except KeyError:
cache[_key] = [route]
2014-07-17 16:09:24 +00:00
routes = cache[key]
2014-07-09 16:17:43 +00:00
for route in routes:
2014-07-17 16:09:24 +00:00
if route.matches(operation.instance):
2014-07-09 16:17:43 +00:00
servers.append(route.host)
return servers
2014-05-08 16:59:35 +00:00
2014-07-17 16:09:24 +00:00
def matches(self, instance):
safe_locals = {
2014-10-04 09:29:18 +00:00
'instance': instance,
'obj': instance,
instance._meta.model_name: instance,
2014-07-17 16:09:24 +00:00
}
return eval(self.match, safe_locals)
2014-07-10 10:03:22 +00:00
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
2014-05-08 16:59:35 +00:00
2014-07-10 10:03:22 +00:00
# def method_class(self):
2014-05-08 16:59:35 +00:00
# for method in MethodBackend.get_backends():
# if method.get_name() == self.method:
# return method
# raise ValueError('This method is not registered')
def enable(self):
self.is_active = True
self.save()
def disable(self):
self.is_active = False
self.save()