"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/common/manager.py" (13 May 2020, 7780 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "manager.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 # Copyright 2012 OpenStack Foundation
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 # not use this file except in compliance with the License. You may obtain
    5 # a copy of the License at
    6 #
    7 #      http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 # License for the specific language governing permissions and limitations
   13 # under the License.
   14 
   15 import functools
   16 import inspect
   17 import time
   18 import types
   19 
   20 from oslo_log import log
   21 import stevedore
   22 
   23 from keystone.common import provider_api
   24 from keystone.i18n import _
   25 
   26 
   27 LOG = log.getLogger(__name__)
   28 
   29 if hasattr(inspect, 'getfullargspec'):
   30     getargspec = inspect.getfullargspec
   31 else:
   32     getargspec = inspect.getargspec
   33 
   34 
   35 def response_truncated(f):
   36     """Truncate the list returned by the wrapped function.
   37 
   38     This is designed to wrap Manager list_{entity} methods to ensure that
   39     any list limits that are defined are passed to the driver layer.  If a
   40     hints list is provided, the wrapper will insert the relevant limit into
   41     the hints so that the underlying driver call can try and honor it. If the
   42     driver does truncate the response, it will update the 'truncated' attribute
   43     in the 'limit' entry in the hints list, which enables the caller of this
   44     function to know if truncation has taken place.  If, however, the driver
   45     layer is unable to perform truncation, the 'limit' entry is simply left in
   46     the hints list for the caller to handle.
   47 
   48     A _get_list_limit() method is required to be present in the object class
   49     hierarchy, which returns the limit for this backend to which we will
   50     truncate.
   51 
   52     If a hints list is not provided in the arguments of the wrapped call then
   53     any limits set in the config file are ignored.  This allows internal use
   54     of such wrapped methods where the entire data set is needed as input for
   55     the calculations of some other API (e.g. get role assignments for a given
   56     project).
   57 
   58     """
   59     @functools.wraps(f)
   60     def wrapper(self, *args, **kwargs):
   61         if kwargs.get('hints') is None:
   62             return f(self, *args, **kwargs)
   63 
   64         list_limit = self.driver._get_list_limit()
   65         if list_limit:
   66             kwargs['hints'].set_limit(list_limit)
   67         return f(self, *args, **kwargs)
   68     return wrapper
   69 
   70 
   71 def load_driver(namespace, driver_name, *args):
   72     try:
   73         driver_manager = stevedore.DriverManager(namespace,
   74                                                  driver_name,
   75                                                  invoke_on_load=True,
   76                                                  invoke_args=args)
   77         return driver_manager.driver
   78     except stevedore.exception.NoMatches:
   79         msg = (_('Unable to find %(name)r driver in %(namespace)r.'))
   80         raise ImportError(msg % {'name': driver_name, 'namespace': namespace})
   81 
   82 
   83 class _TraceMeta(type):
   84     """A metaclass that, in trace mode, will log entry and exit of methods.
   85 
   86     This metaclass automatically wraps all methods on the class when
   87     instantiated with a decorator that will log entry/exit from a method
   88     when keystone is run in Trace log level.
   89     """
   90 
   91     @staticmethod
   92     def wrapper(__f, __classname):
   93         __argspec = getargspec(__f)
   94         __fn_info = '%(module)s.%(classname)s.%(funcname)s' % {
   95             'module': inspect.getmodule(__f).__name__,
   96             'classname': __classname,
   97             'funcname': __f.__name__
   98         }
   99         # NOTE(morganfainberg): Omit "cls" and "self" when printing trace logs
  100         # the index can be calculated at wrap time rather than at runtime.
  101         if __argspec.args and __argspec.args[0] in ('self', 'cls'):
  102             __arg_idx = 1
  103         else:
  104             __arg_idx = 0
  105 
  106         @functools.wraps(__f)
  107         def wrapped(*args, **kwargs):
  108             __exc = None
  109             __t = time.time()
  110             __do_trace = LOG.logger.getEffectiveLevel() <= log.TRACE
  111             __ret_val = None
  112             try:
  113                 if __do_trace:
  114                     LOG.trace('CALL => %s', __fn_info)
  115                 __ret_val = __f(*args, **kwargs)
  116             except Exception as e:  # nosec
  117                 __exc = e
  118                 raise
  119             finally:
  120                 if __do_trace:
  121                     __subst = {
  122                         'run_time': (time.time() - __t),
  123                         'passed_args': ', '.join([
  124                             ', '.join([repr(a)
  125                                        for a in args[__arg_idx:]]),
  126                             ', '.join(['%(k)s=%(v)r' % {'k': k, 'v': v}
  127                                        for k, v in kwargs.items()]),
  128                         ]),
  129                         'function': __fn_info,
  130                         'exception': __exc,
  131                         'ret_val': __ret_val,
  132                     }
  133                     if __exc is not None:
  134                         __msg = ('[%(run_time)ss] %(function)s '
  135                                  '(%(passed_args)s) => raised '
  136                                  '%(exception)r')
  137                     else:
  138                         # TODO(morganfainberg): find a way to indicate if this
  139                         # was a cache hit or cache miss.
  140                         __msg = ('[%(run_time)ss] %(function)s'
  141                                  '(%(passed_args)s) => %(ret_val)r')
  142                     LOG.trace(__msg, __subst)
  143             return __ret_val
  144         return wrapped
  145 
  146     def __new__(meta, classname, bases, class_dict):
  147         final_cls_dict = {}
  148         for attr_name, attr in class_dict.items():
  149             # NOTE(morganfainberg): only wrap public instances and methods.
  150             if (isinstance(attr, types.FunctionType) and
  151                     not attr_name.startswith('_')):
  152                 attr = _TraceMeta.wrapper(attr, classname)
  153             final_cls_dict[attr_name] = attr
  154         return type.__new__(meta, classname, bases, final_cls_dict)
  155 
  156 
  157 class Manager(object, metaclass=_TraceMeta):
  158     """Base class for intermediary request layer.
  159 
  160     The Manager layer exists to support additional logic that applies to all
  161     or some of the methods exposed by a service that are not specific to the
  162     HTTP interface.
  163 
  164     It also provides a stable entry point to dynamic backends.
  165 
  166     An example of a probable use case is logging all the calls.
  167 
  168     """
  169 
  170     driver_namespace = None
  171     _provides_api = None
  172 
  173     def __init__(self, driver_name):
  174         if self._provides_api is None:
  175             raise ValueError('Programming Error: All managers must provide an '
  176                              'API that can be referenced by other components '
  177                              'of Keystone.')
  178         if driver_name is not None:
  179             self.driver = load_driver(self.driver_namespace, driver_name)
  180         self.__register_provider_api()
  181 
  182     def __register_provider_api(self):
  183         provider_api.ProviderAPIs._register_provider_api(
  184             name=self._provides_api, obj=self)
  185 
  186     def __getattr__(self, name):
  187         """Forward calls to the underlying driver.
  188 
  189         This method checks for a provider api before forwarding.
  190         """
  191         try:
  192             return getattr(provider_api.ProviderAPIs, name)
  193         except AttributeError:
  194             # NOTE(morgan): We didn't find a provider api, move on and
  195             # forward to the driver as expected.
  196             pass
  197 
  198         f = getattr(self.driver, name)
  199         if callable(f):
  200             # NOTE(dstanek): only if this is callable (class or function)
  201             # cache this
  202             setattr(self, name, f)
  203         return f