Files
deb-python-taskflow/taskflow/listeners/base.py
Joshua Harlow 843b487736 Allow jobboard event notification
Instead of requiring iteration to be able to list new
jobs and existing jobs so that they can be claimed, allow
for attaching callbacks to the jobboard where instead of
requiring iteration those callbacks will be automatically
called by the jobboard internally when events are recieved.

Breaking change: renames transition notifier to notifier
since it is really not just a notifier for transitions but
is a generic notifier of events occuring (and details about
those events). This is an internal api so its not expected
that this will cause any issues (it's not expected for external
users to be creating instances of this class).

Implements bp board-notification

Change-Id: I2384d5e335ed9d17e29fec9a78699e3156fa225c
2014-04-21 18:40:14 -07:00

157 lines
5.2 KiB
Python

# -*- 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)