def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_when_all, desired_states)) handler.add_args( filter(None, map(RelationBase.from_state, desired_states))) handler.register_states(desired_states) return action
def _when_decorator(predicate, desired_flags, action, legacy_args=False): endpoint_names = _get_endpoint_names(action) has_relname_flag = _has_endpoint_name_flag(desired_flags) params = signature(action).parameters has_params = len(params) > 0 if has_relname_flag and not endpoint_names: # If this is an Endpoint handler but there are no endpoints # for this interface & role, then we shouldn't register its # handlers. It probably means we only use a different role. return action for endpoint_name in endpoint_names or [None]: handler = Handler.get(action, endpoint_name) flags = _expand_endpoint_name(endpoint_name, desired_flags) handler.add_predicate(partial(predicate, flags)) if _is_endpoint_method(action): # Endpoint handler methods expect self to be passed in to conform # to instance method convention. But mutliple decorators should # take care to not pass in multiple copies of self. if not handler.has_args: handler.add_args(map(endpoint_from_name, [endpoint_name])) elif has_params and legacy_args: # Handlers should all move to not taking any params and getting # the Endpoint instances from the context, but during the # transition, we need to provide for handlers expecting args. handler.add_args(filter(None, map(endpoint_from_flag, flags))) handler.register_flags(flags) return action
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
def only_once(action=None): """ Register the decorated function to be run once, and only once. This decorator will never cause arguments to be passed to the handler. """ if action is None: # allow to be used as @only_once or @only_once() return only_once action_id = _action_id(action) handler = Handler.get(action) handler.add_predicate(lambda: not was_invoked(action_id)) handler.add_post_callback(partial(mark_invoked, action_id)) return action
def only_once(action=None): """ .. deprecated:: 0.5.0 Use :func:`when_not` in combination with :func:`set_state` instead. This handler is deprecated because it might actually be `called multiple times <https://github.com/juju-solutions/charms.reactive/issues/22>`_. Register the decorated function to be run once, and only once. This decorator will never cause arguments to be passed to the handler. """ if action is None: # allow to be used as @only_once or @only_once() return only_once action_id = _action_id(action) handler = Handler.get(action) handler.add_predicate(lambda: not was_invoked(action_id)) handler.add_post_callback(partial(mark_invoked, action_id)) return action
def everyhook(action): ''' Decorator to run a handler each and every hook, during the hook phase. Charms should to minimize code that can only run in a specific hook to avoid race conditions. For example, consider a simple service configuration change. This will queue the config-changed hook on each unit, but if there is already a queue of hooks being processed by a unit then these hooks will see the changed configuration in the hook environment before the config-changed hook has had a chance to run. This can be fatal, with hooks crashing attempting to run code paths before the config-changed hook has set things up so they can be run successfully. Similar races can be described for leadership and relations. The simplest way of avoiding this entire class of races is to have a single, general hook instead of several specific ones tied to particular events. ''' def in_hook_phase(): dispatch_phase = unitdata.kv().get('reactive.dispatch.phase') return dispatch_phase == 'hooks' handler = Handler.get(action) handler.add_predicate(in_hook_phase) return action
def everyhook(action): """ Decorator to run a handler each and every hook, during the hook phase. Charms should to minimize code that can only run in a specific hook to avoid race conditions. For example, consider a simple service configuration change. This will queue the config-changed hook on each unit, but if there is already a queue of hooks being processed by a unit then these hooks will see the changed configuration in the hook environment before the config-changed hook has had a chance to run. This can be fatal, with hooks crashing attempting to run code paths before the config-changed hook has set things up so they can be run successfully. Similar races can be described for leadership and relations. The simplest way of avoiding this entire class of races is to have a single, general hook instead of several specific ones tied to particular events. """ def in_hook_phase(): dispatch_phase = unitdata.kv().get("reactive.dispatch.phase") return dispatch_phase == "hooks" handler = Handler.get(action) handler.add_predicate(in_hook_phase) return action
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_when, desired_states, True)) handler.register_states(desired_states) return action
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_when, desired_states, False)) handler.add_args(filter(None, map(RelationBase.from_state, desired_states))) handler.register_states(desired_states) return action
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_restricted_hook, 'collect-metrics')) return action
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(any_file_changed, filenames, **kwargs)) return action
def test_handlers(self): Handler._HANDLERS = { k: h for k, h in Handler._HANDLERS.items() if hasattr(h, '_action') and h._action.__qualname__.startswith('TestAltRequires.') } assert Handler._HANDLERS preds = [h._predicates[0].args[0][0] for h in Handler.get_handlers()] for pred in preds: self.assertRegex(pred, r'^endpoint.test-endpoint.') self.data_changed.return_value = False Endpoint._startup() tep = Endpoint.from_name('test-endpoint') self.assertCountEqual(tep.invocations, []) dispatch() self.assertCountEqual(tep.invocations, [ 'joined: test-endpoint', ]) tep.invocations.clear() clear_flag('endpoint.test-endpoint.joined') clear_flag('endpoint.test-endpoint.changed') clear_flag('endpoint.test-endpoint.changed.foo') clear_flag('endpoint.test-endpoint2.joined') clear_flag('endpoint.test-endpoint2.changed') clear_flag('endpoint.test-endpoint2.changed.foo') self.data_changed.return_value = True Endpoint._startup() dispatch() self.assertCountEqual(tep.invocations, [ 'joined: test-endpoint', 'changed: test-endpoint', 'changed.foo: test-endpoint', ]) tep.invocations.clear() clear_flag('endpoint.test-endpoint.joined') clear_flag('endpoint.test-endpoint.changed') clear_flag('endpoint.test-endpoint.changed.foo') clear_flag('endpoint.test-endpoint2.joined') clear_flag('endpoint.test-endpoint2.changed') clear_flag('endpoint.test-endpoint2.changed.foo') self.relations['test-endpoint2'] = [ { 'unit/0': { 'foo': 'yes' }, 'unit/1': {}, }, { 'unit/0': {}, 'unit/1': { 'foo': 'no' }, }, ] Endpoint._startup() dispatch() self.assertCountEqual(tep.invocations, [ 'joined: test-endpoint', 'joined: test-endpoint2', 'changed: test-endpoint', 'changed: test-endpoint2', 'changed.foo: test-endpoint', 'changed.foo: test-endpoint2', ])
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_restricted_hook, 'meter-status-changed')) return action
def _register(action): handler = Handler.get(action) handler.add_predicate(partial(_when_not_all, desired_states)) handler.register_states(desired_states) return action