import imp import logging from collections import defaultdict from cloudcontrol.common.client.exc import TagConflict 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.tags = [] 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__ conflicts = self.tag_db.check_tags_conflict(*[tag.name for tag in self.tags]) if conflicts: raise TagConflict( 'Tags with names %s already exist' % ','.join(conflicts)) else: self.tag_db.add_tags(self.tags) except Exception as exc: err_msg = 'Error during plugin installation (%s): %s' % ( self.name, exc) logger.exception(err_msg) # make sure all tags are stopped self.tag_db.set_parent(None) 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.tags.append(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)