# -*- coding: utf-8 -*- # Copyright (C) 2013 Yahoo! Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import absolute_import import abc import logging import six from taskflow.openstack.common import excutils from taskflow import states from taskflow.utils import misc LOG = logging.getLogger(__name__) # NOTE(harlowja): on these states will results be usable, all other states # do not produce results. FINISH_STATES = (states.FAILURE, states.SUCCESS) class ListenerBase(object): """Base class for listeners. A listener can be attached to an engine to do various actions on flow and task state transitions. It implements context manager protocol to be able to register and unregister with a given engine automatically when a context is entered and when it is exited. To implement a listener, derive from this class and override ``_flow_receiver`` and/or ``_task_receiver`` methods (in this class, they do nothing). """ def __init__(self, engine, task_listen_for=(misc.Notifier.ANY,), flow_listen_for=(misc.Notifier.ANY,)): if not task_listen_for: task_listen_for = [] if not flow_listen_for: flow_listen_for = [] self._listen_for = { 'task': list(task_listen_for), 'flow': list(flow_listen_for), } self._engine = engine self._registered = False def _flow_receiver(self, state, details): pass def _task_receiver(self, state, details): pass def deregister(self): if not self._registered: return def _deregister(watch_states, notifier, cb): for s in watch_states: notifier.deregister(s, cb) _deregister(self._listen_for['task'], self._engine.task_notifier, self._task_receiver) _deregister(self._listen_for['flow'], self._engine.notifier, self._flow_receiver) self._registered = False def register(self): if self._registered: return def _register(watch_states, notifier, cb): registered = [] try: for s in watch_states: if not notifier.is_registered(s, cb): notifier.register(s, cb) registered.append((s, cb)) except ValueError: with excutils.save_and_reraise_exception(): for (s, cb) in registered: notifier.deregister(s, cb) _register(self._listen_for['task'], self._engine.task_notifier, self._task_receiver) _register(self._listen_for['flow'], self._engine.notifier, self._flow_receiver) self._registered = True def __enter__(self): self.register() def __exit__(self, type, value, tb): try: self.deregister() except Exception: # Don't let deregistering throw exceptions LOG.warn("Failed deregistering listeners from engine %s", self._engine, exc_info=True) @six.add_metaclass(abc.ABCMeta) class LoggingBase(ListenerBase): """Abstract base class for logging listeners. This provides a simple listener that can be attached to an engine which can be derived from to log task and/or flow state transitions to some logging backend. To implement your own logging listener derive form this class and override ``_log`` method. """ @abc.abstractmethod def _log(self, message, *args, **kwargs): raise NotImplementedError() def _flow_receiver(self, state, details): self._log("%s has moved flow '%s' (%s) into state '%s'", self._engine, details['flow_name'], details['flow_uuid'], state) def _task_receiver(self, state, details): if state in FINISH_STATES: result = details.get('result') exc_info = None was_failure = False if isinstance(result, misc.Failure): if result.exc_info: exc_info = tuple(result.exc_info) was_failure = True self._log("%s has moved task '%s' (%s) into state '%s'" " with result '%s' (failure=%s)", self._engine, details['task_name'], details['task_uuid'], state, result, was_failure, exc_info=exc_info) else: self._log("%s has moved task '%s' (%s) into state '%s'", self._engine, details['task_name'], details['task_uuid'], state)