aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/manager.py
blob: 4ce9f2a621bb1ed850df82f32c9d18770ca54b80 (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# Copyright 2012 OpenStack Foundation
#
# 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.

import functools
import inspect
import time
import types

from oslo_log import log
from oslo_log import versionutils
from oslo_utils import importutils
from oslo_utils import reflection
import six
import stevedore

from keystone.i18n import _


LOG = log.getLogger(__name__)


def response_truncated(f):
    """Truncate the list returned by the wrapped function.

    This is designed to wrap Manager list_{entity} methods to ensure that
    any list limits that are defined are passed to the driver layer.  If a
    hints list is provided, the wrapper will insert the relevant limit into
    the hints so that the underlying driver call can try and honor it. If the
    driver does truncate the response, it will update the 'truncated' attribute
    in the 'limit' entry in the hints list, which enables the caller of this
    function to know if truncation has taken place.  If, however, the driver
    layer is unable to perform truncation, the 'limit' entry is simply left in
    the hints list for the caller to handle.

    A _get_list_limit() method is required to be present in the object class
    hierarchy, which returns the limit for this backend to which we will
    truncate.

    If a hints list is not provided in the arguments of the wrapped call then
    any limits set in the config file are ignored.  This allows internal use
    of such wrapped methods where the entire data set is needed as input for
    the calculations of some other API (e.g. get role assignments for a given
    project).

    """
    @functools.wraps(f)
    def wrapper(self, *args, **kwargs):
        if kwargs.get('hints') is None:
            return f(self, *args, **kwargs)

        list_limit = self.driver._get_list_limit()
        if list_limit:
            kwargs['hints'].set_limit(list_limit)
        return f(self, *args, **kwargs)
    return wrapper


def load_driver(namespace, driver_name, *args):
    try:
        driver_manager = stevedore.DriverManager(namespace,
                                                 driver_name,
                                                 invoke_on_load=True,
                                                 invoke_args=args)
        return driver_manager.driver
    except RuntimeError as e:
        LOG.debug('Failed to load %r using stevedore: %s', driver_name, e)
        # Ignore failure and continue on.

    driver = importutils.import_object(driver_name, *args)

    msg = (_(
        'Direct import of driver %(name)r is deprecated as of Liberty in '
        'favor of its entrypoint from %(namespace)r and may be removed in '
        'N.') %
        {'name': driver_name, 'namespace': namespace})
    versionutils.report_deprecated_feature(LOG, msg)

    return driver


class _TraceMeta(type):
    """A metaclass that, in trace mode, will log entry and exit of methods.

    This metaclass automatically wraps all methods on the class when
    instantiated with a decorator that will log entry/exit from a method
    when keystone is run in Trace log level.
    """

    @staticmethod
    def wrapper(__f, __classname):
        __argspec = inspect.getargspec(__f)
        __fn_info = '%(module)s.%(classname)s.%(funcname)s' % {
            'module': inspect.getmodule(__f).__name__,
            'classname': __classname,
            'funcname': __f.__name__
        }
        # NOTE(morganfainberg): Omit "cls" and "self" when printing trace logs
        # the index can be calculated at wrap time rather than at runtime.
        if __argspec.args and __argspec.args[0] in ('self', 'cls'):
            __arg_idx = 1
        else:
            __arg_idx = 0

        @functools.wraps(__f)
        def wrapped(*args, **kwargs):
            __exc = None
            __t = time.time()
            __do_trace = LOG.logger.getEffectiveLevel() <= log.TRACE
            __ret_val = None
            try:
                if __do_trace:
                    LOG.trace('CALL => %s', __fn_info)
                __ret_val = __f(*args, **kwargs)
            except Exception as e:  # nosec
                __exc = e
                raise
            finally:
                if __do_trace:
                    __subst = {
                        'run_time': (time.time() - __t),
                        'passed_args': ', '.join([
                            ', '.join([repr(a)
                                       for a in args[__arg_idx:]]),
                            ', '.join(['%(k)s=%(v)r' % {'k': k, 'v': v}
                                       for k, v in kwargs.items()]),
                        ]),
                        'function': __fn_info,
                        'exception': __exc,
                        'ret_val': __ret_val,
                    }
                    if __exc is not None:
                        __msg = ('[%(run_time)ss] %(function)s '
                                 '(%(passed_args)s) => raised '
                                 '%(exception)r')
                    else:
                        # TODO(morganfainberg): find a way to indicate if this
                        # was a cache hit or cache miss.
                        __msg = ('[%(run_time)ss] %(function)s'
                                 '(%(passed_args)s) => %(ret_val)r')
                    LOG.trace(__msg, __subst)
            return __ret_val
        return wrapped

    def __new__(meta, classname, bases, class_dict):
        final_cls_dict = {}
        for attr_name, attr in class_dict.items():
            # NOTE(morganfainberg): only wrap public instances and methods.
            if (isinstance(attr, types.FunctionType) and
                    not attr_name.startswith('_')):
                attr = _TraceMeta.wrapper(attr, classname)
            final_cls_dict[attr_name] = attr
        return type.__new__(meta, classname, bases, final_cls_dict)


@six.add_metaclass(_TraceMeta)
class Manager(object):
    """Base class for intermediary request layer.

    The Manager layer exists to support additional logic that applies to all
    or some of the methods exposed by a service that are not specific to the
    HTTP interface.

    It also provides a stable entry point to dynamic backends.

    An example of a probable use case is logging all the calls.

    """

    driver_namespace = None

    def __init__(self, driver_name):
        self.driver = load_driver(self.driver_namespace, driver_name)

    def __getattr__(self, name):
        """Forward calls to the underlying driver."""
        f = getattr(self.driver, name)
        setattr(self, name, f)
        return f


def create_legacy_driver(driver_class):
    """Helper function to deprecate the original driver classes.

    The keystone.{subsystem}.Driver classes are deprecated in favor of the
    new versioned classes. This function creates a new class based on a
    versioned class and adds a deprecation message when it is used.

    This will allow existing custom drivers to work when the Driver class is
    renamed to include a version.

    Example usage:

        Driver = create_legacy_driver(CatalogDriverV8)

    """
    module_name = driver_class.__module__
    class_name = reflection.get_class_name(driver_class)

    class Driver(driver_class):

        @versionutils.deprecated(
            as_of=versionutils.deprecated.LIBERTY,
            what='%s.Driver' % module_name,
            in_favor_of=class_name,
            remove_in=+2)
        def __init__(self, *args, **kwargs):
            super(Driver, self).__init__(*args, **kwargs)

    return Driver