Newer
Older
# This file is part of CloudControl.
#
# CloudControl is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CloudControl is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CloudControl. If not, see <http://www.gnu.org/licenses/>.
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, background=False):
def decorator(func):
tag = Tag(func.__name__ if name is None else name,
lambda: taggify(func()), ttl=ttl, refresh=refresh,
background=background)
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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)