OpenStack Identity (Keystone)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
7.6KB

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