Newer
Older
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
from cloudcontrol.node.exc import PluginError
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__
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)
# 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):
register_name = func.__name__ if name is None else name
if register_name in self.methods:
raise PluginError('Already defined method %s' % register_name)
self.methods[register_name] = func
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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)