159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
# Copyright 2014-2015 Canonical Limited.
|
|
#
|
|
# This file is part of charm-helpers.
|
|
#
|
|
# charm-helpers is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# charm-helpers 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 charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from six.moves import filter, map
|
|
from functools import wraps, partial
|
|
|
|
from charmhelpers.core import hookenv
|
|
from charmhelpers.core.reactive.bus import Handler
|
|
from charmhelpers.core.reactive.bus import get_states
|
|
from charmhelpers.core.reactive.bus import _action_id
|
|
from charmhelpers.core.reactive.relations import RelationBase
|
|
from charmhelpers.core.reactive.helpers import _hook
|
|
from charmhelpers.core.reactive.helpers import _when
|
|
from charmhelpers.core.reactive.helpers import any_file_changed
|
|
from charmhelpers.core.reactive.helpers import was_invoked
|
|
from charmhelpers.core.reactive.helpers import mark_invoked
|
|
|
|
|
|
def hook(*hook_patterns):
|
|
"""
|
|
Register the decorated function to run when the current hook matches any of
|
|
the ``hook_patterns``.
|
|
|
|
The hook patterns can use the ``{interface:...}`` and ``{A,B,...}`` syntax
|
|
supported by :func:`~charmhelpers.core.reactive.bus.any_hook`.
|
|
|
|
If the hook is a relation hook, an instance of that relation class will be
|
|
passed in to the decorated function.
|
|
|
|
For example, to match any joined or changed hook for the relation providing
|
|
the ``mysql`` interface::
|
|
|
|
class MySQLRelation(RelationBase):
|
|
@hook('{provides:mysql}-relation-{joined,changed}')
|
|
def joined_or_changed(self):
|
|
pass
|
|
"""
|
|
def _register(action):
|
|
def arg_gen():
|
|
# use a generator to defer calling of hookenv.relation_type, for tests
|
|
rel = RelationBase.from_name(hookenv.relation_type())
|
|
if rel:
|
|
yield rel
|
|
|
|
handler = Handler.get(action)
|
|
handler.add_predicate(partial(_hook, hook_patterns))
|
|
handler.add_args(arg_gen())
|
|
return action
|
|
return _register
|
|
|
|
|
|
def when(*desired_states):
|
|
"""
|
|
Register the decorated function to run when all ``desired_states`` are active.
|
|
|
|
This decorator will pass zero or more relation instances to the handler, if
|
|
any of the states are associated with relations. If so, they will be passed
|
|
in in the same order that the states are given to the decorator.
|
|
|
|
Note that handlers whose conditions match are triggered at least once per
|
|
hook invocation.
|
|
"""
|
|
def _register(action):
|
|
handler = Handler.get(action)
|
|
handler.add_predicate(partial(_when, _action_id(action), desired_states, False))
|
|
handler.add_args(filter(None, map(RelationBase.from_state, desired_states)))
|
|
return action
|
|
return _register
|
|
|
|
|
|
def when_not(*desired_states):
|
|
"""
|
|
Register the decorated function to run when **not** all desired_states are active.
|
|
|
|
This decorator will never cause arguments to be passed to the handler.
|
|
|
|
Note that handlers whose conditions match are triggered at least once per
|
|
hook invocation.
|
|
"""
|
|
def _register(action):
|
|
handler = Handler.get(action)
|
|
handler.add_predicate(partial(_when, _action_id(action), desired_states, True))
|
|
return action
|
|
return _register
|
|
|
|
|
|
def when_file_changed(*filenames, **kwargs):
|
|
"""
|
|
Register the decorated function to run when one or more files have changed.
|
|
|
|
:param list filenames: The names of one or more files to check for changes.
|
|
:param str hash_type: The type of hash to use for determining if a file has
|
|
changed. Defaults to 'md5'. Must be given as a kwarg.
|
|
"""
|
|
def _register(action):
|
|
handler = Handler.get(action)
|
|
handler.add_predicate(partial(any_file_changed, filenames, **kwargs))
|
|
return action
|
|
return _register
|
|
|
|
|
|
def not_unless(*desired_states):
|
|
"""
|
|
Assert that the decorated function can only be called if the desired_states
|
|
are active.
|
|
|
|
Note that, unlike :func:`when`, this does **not** trigger the decorated
|
|
function if the states match. It **only** raises an exception if the
|
|
function is called when the states do not match.
|
|
|
|
This is primarily for informational purposes and as a guard clause.
|
|
"""
|
|
def _decorator(func):
|
|
@wraps(func)
|
|
def _wrapped(*args, **kwargs):
|
|
active_states = get_states()
|
|
missing_states = [state for state in desired_states if state not in active_states]
|
|
if missing_states:
|
|
func_id = "%s:%s:%s" % (func.__code__.co_filename,
|
|
func.__code__.co_firstlineno,
|
|
func.__code__.co_name)
|
|
hookenv.log('%s called before state%s: %s' % (
|
|
func_id,
|
|
's' if len(missing_states) > 1 else '',
|
|
', '.join(missing_states)), hookenv.WARNING)
|
|
return func(*args, **kwargs)
|
|
return _wrapped
|
|
return _decorator
|
|
|
|
|
|
def only_once(action):
|
|
"""
|
|
Ensure that the decorated function is only executed the first time it is called.
|
|
|
|
This can be used on reactive handlers to ensure that they are only triggered
|
|
once, even if their conditions continue to match on subsequent calls, even
|
|
across hook invocations.
|
|
"""
|
|
@wraps(action)
|
|
def wrapper(*args, **kwargs):
|
|
action_id = _action_id(action)
|
|
if not was_invoked(action_id):
|
|
action(*args, **kwargs)
|
|
mark_invoked(action_id)
|
|
return wrapper
|