Commit e98382b6 authored by Antoine Millet's avatar Antoine Millet Committed by Anael Beutot
Browse files

Added plugins module in host plugin

This module provide a class representing a loaded plugin, the method
execution job and some helpers.
parent ae27845d
Loading
Loading
Loading
Loading
+93 −0
Changes for cloudcontrol/node/host/plugins.py: 93 added lines, 0 removed lines.
Original line number Diff line number Diff line
import imp
import logging
from collections import defaultdict

from cloudcontrol.common.client.tags import Tag
from cloudcontrol.common.jobs import Job
from cloudcontrol.common.tql.db.helpers import taggify


logger = logging.getLogger(__name__)


def _tag_direct_use_error(*args, **kwargs):
    raise RuntimeError('Plugins tags can\'t be called directly')


class Plugin(object):

    """ Represent a loaded cloudcontrol plugin.
    """

    def __init__(self, logger, tag_db, name, sha1, plugin_body):
        self.logger = logger
        self.name = name
        self.sha1 = sha1
        self.tag_db = tag_db
        self.methods = {}
        self.events = defaultdict(lambda: [])
        self._load_module(plugin_body)
        self.trigger('install', self)

    def _load_module(self, plugin_body):
        # load the code into a module:
        module = imp.new_module(self.name)
        module.logger = self.logger
        module.tag = self._decorator_tag
        module.method = self._decorator_method
        module.on = self._decorator_event
        try:
            exec plugin_body in module.__dict__
        except Exception:
            err_msg = 'Error during plugin installation (%s)' % self.name
            logger.exception(err_msg)
            raise RuntimeError(err_msg)
        else:
            # prevents module from being garbage collected
            self.module = module

    def _decorator_tag(self, ttl=-1, refresh=None, name=None):
        def decorator(func):
            tag = Tag(func.__name__ if name is None else name,
                      lambda: taggify(func()), ttl=ttl, refresh=refresh)
            self.tag_db.add_tag(tag)
            return _tag_direct_use_error
        return decorator

    def _decorator_method(self, name=None):
        def decorator(func):
            self.methods[func.__name__ if name is None else name] = func
            return func
        return decorator

    def _decorator_event(self, event):
        def decorator(func):
            self.events[event].append(func)
            return func
        return decorator

    def trigger(self, event, *args, **kwargs):
        """Trigger an event."""
        for func in self.events[event]:
            func(*args, **kwargs)

    def uninstall(self):
        self.trigger('uninstall')
        # shutdown the tags database:
        self.tag_db.set_parent(None)


class PluginMethodJob(Job):

    """Job that run a plugin method."""

    def job(self, plugin_name, method_name, method, method_kwargs):
        kwargs_str = ', '.join(('%s=%s' % (k, v) for k, v in method_kwargs.iteritems()))
        self.title = 'Plugin: %s.%s(%s)' % (plugin_name, method_name, kwargs_str)
        try:
            returned = method(self, **method_kwargs)
        except Exception:
            self.logger.exception('Error while executing method')
            raise
        if returned is not None:
            self.attachment('output').write(returned)