summaryrefslogtreecommitdiff
path: root/nova/hooks.py
blob: 735b89338b44d8e63da5325b00313c506a63b381 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# Copyright (c) 2012 OpenStack Foundation
# 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.

"""Decorator and config option definitions for adding custom code (hooks)
around callables.

Any method may have the 'add_hook' decorator applied, which yields the
ability to invoke Hook objects before or after the method. (i.e. pre and
post)

Hook objects are loaded by HookLoaders.  Each named hook may invoke multiple
Hooks.

Example Hook object::

    | class MyHook(object):
    |    def pre(self, *args, **kwargs):
    |       # do stuff before wrapped callable runs
    |
    |   def post(self, rv, *args, **kwargs):
    |       # do stuff after wrapped callable runs

Example Hook object with function parameters::

    | class MyHookWithFunction(object):
    |   def pre(self, f, *args, **kwargs):
    |       # do stuff with wrapped function info
    |   def post(self, f, *args, **kwargs):
    |       # do stuff with wrapped function info

"""

import functools

import stevedore

from nova.i18n import _LE
from nova.openstack.common import log as logging

LOG = logging.getLogger(__name__)
NS = 'nova.hooks'

_HOOKS = {}  # hook name => hook manager


class FatalHookException(Exception):
    """Exception which should be raised by hooks to indicate that normal
    execution of the hooked function should be terminated. Raised exception
    will be logged and reraised.
    """
    pass


class HookManager(stevedore.hook.HookManager):
    def __init__(self, name):
        # invoke_on_load creates an instance of the Hook class
        super(HookManager, self).__init__(NS, name, invoke_on_load=True)

    def run_pre(self, name, args, kwargs, f=None):
        for e in self.extensions:
            obj = e.obj
            pre = getattr(obj, 'pre', None)
            if pre:
                LOG.debug("Running %(name)s pre-hook: %(obj)s",
                          {'name': name, 'obj': obj})
                try:
                    if f:
                        pre(f, *args, **kwargs)
                    else:
                        pre(*args, **kwargs)
                except FatalHookException:
                    msg = _LE("Fatal Exception running %(name)s "
                              "pre-hook: %(obj)s")
                    LOG.exception(msg, {'name': name, 'obj': obj})
                    raise
                except Exception:
                    msg = _LE("Exception running %(name)s pre-hook: %(obj)s")
                    LOG.exception(msg, {'name': name, 'obj': obj})

    def run_post(self, name, rv, args, kwargs, f=None):
        for e in reversed(self.extensions):
            obj = e.obj
            post = getattr(obj, 'post', None)
            if post:
                LOG.debug("Running %(name)s post-hook: %(obj)s",
                          {'name': name, 'obj': obj})
                try:
                    if f:
                        post(f, rv, *args, **kwargs)
                    else:
                        post(rv, *args, **kwargs)
                except FatalHookException:
                    msg = _LE("Fatal Exception running %(name)s "
                              "post-hook: %(obj)s")
                    LOG.exception(msg, {'name': name, 'obj': obj})
                    raise
                except Exception:
                    msg = _LE("Exception running %(name)s post-hook: %(obj)s")
                    LOG.exception(msg, {'name': name, 'obj': obj})


def add_hook(name, pass_function=False):
    """Execute optional pre and post methods around the decorated
    function.  This is useful for customization around callables.
    """

    def outer(f):
        f.__hook_name__ = name

        @functools.wraps(f)
        def inner(*args, **kwargs):
            manager = _HOOKS.setdefault(name, HookManager(name))

            function = None
            if pass_function:
                function = f

            manager.run_pre(name, args, kwargs, f=function)
            rv = f(*args, **kwargs)
            manager.run_post(name, rv, args, kwargs, f=function)

            return rv

        return inner
    return outer


def reset():
    """Clear loaded hooks."""
    _HOOKS.clear()