PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/neutron/api/rpc/callbacks/version_manager.py

https://github.com/openstack/neutron
Python | 260 lines | 188 code | 18 blank | 54 comment | 6 complexity | b36c3bc4e1df0493e4e531cf9ebe248b MD5 | raw file
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import collections
  13. import copy
  14. import pprint
  15. import time
  16. from neutron_lib.plugins import directory
  17. from oslo_log import log as logging
  18. from oslo_utils import importutils
  19. from neutron.api.rpc.callbacks import exceptions
  20. LOG = logging.getLogger(__name__)
  21. VERSIONS_TTL = 60
  22. # NOTE(mangelajo): if we import this globally we end up with a (very
  23. # long) circular dependency, this can be fixed if we
  24. # stop importing all exposed classes in
  25. # neutron.api.rpc.callbacks.resources and provide
  26. # a decorator to expose classes
  27. def _import_resources():
  28. return importutils.import_module('neutron.api.rpc.callbacks.resources')
  29. def _import_agents_db():
  30. return importutils.import_module('neutron.db.agents_db')
  31. AgentConsumer = collections.namedtuple('AgentConsumer', ['agent_type',
  32. 'host'])
  33. AgentConsumer.__repr__ = lambda self: '%s@%s' % self
  34. class ResourceConsumerTracker(object):
  35. """Class passed down to collect consumer's resource versions.
  36. This class is responsible for fetching the local versions of
  37. resources, and letting the called function register every consumer's
  38. resource version.
  39. This class is passed down to the plugin get_agents_resource_versions
  40. currently, as the only expected consumers are agents so far.
  41. Later on, this class can also be used to recalculate, for each
  42. resource type, the collection of versions that are local or
  43. known by one or more consumers.
  44. """
  45. def __init__(self):
  46. # Initialize with the local (server) versions, as we always want
  47. # to send those. Agents, as they upgrade, will need the latest version,
  48. # and there is a corner case we'd not be covering otherwise:
  49. # 1) one or several neutron-servers get disconnected from rpc (while
  50. # running)
  51. # 2) a new agent comes up, with the latest version and it reports
  52. # 2 ways:
  53. # a) via status report (which will be stored in the database)
  54. # b) via fanout call to all neutron servers, this way, all of them
  55. # get their version set updated right away without the need to
  56. # re-fetch anything from the database.
  57. # 3) the neutron-servers get back online to the rpc bus, but they
  58. # lost the fanout message.
  59. #
  60. # TODO(mangelajo) To cover this case we may need a callback from oslo
  61. # messaging to get notified about disconnections/reconnections to the
  62. # rpc bus, invalidating the consumer version cache when we receive such
  63. # callback.
  64. self._versions = self._get_local_resource_versions()
  65. self._versions_by_consumer = collections.defaultdict(dict)
  66. self._needs_recalculation = False
  67. self.last_report = None
  68. def _get_local_resource_versions(self):
  69. resources = _import_resources()
  70. local_resource_versions = collections.defaultdict(set)
  71. for resource_type, version in (
  72. resources.LOCAL_RESOURCE_VERSIONS.items()):
  73. local_resource_versions[resource_type].add(version)
  74. return local_resource_versions
  75. # TODO(mangelajo): add locking with _recalculate_versions if we ever
  76. # move out of green threads.
  77. def _set_version(self, consumer, resource_type, version):
  78. """Set or update a consumer resource type version."""
  79. self._versions[resource_type].add(version)
  80. consumer_versions = self._versions_by_consumer[consumer]
  81. prev_version = consumer_versions.get(resource_type, None)
  82. if version:
  83. consumer_versions[resource_type] = version
  84. else:
  85. consumer_versions.pop(resource_type, None)
  86. if prev_version != version:
  87. # If a version got updated/changed in a consumer, we need to
  88. # recalculate the main dictionary of versions based on the
  89. # new _versions_by_consumer.
  90. # We defer the recalculation until every consumer version has
  91. # been set for all of its resource types.
  92. self._needs_recalculation = True
  93. LOG.debug("Version for resource type %(resource_type)s changed "
  94. "%(prev_version)s to %(version)s on "
  95. "consumer %(consumer)s",
  96. {'resource_type': resource_type,
  97. 'version': version,
  98. 'prev_version': prev_version,
  99. 'consumer': consumer})
  100. def set_versions(self, consumer, versions):
  101. """Set or update an specific consumer resource types.
  102. :param consumer: should be an AgentConsumer object, with agent_type
  103. and host set. This acts as the unique ID for the
  104. agent.
  105. :param versions: should be a dictionary in the following format:
  106. {'QosPolicy': '1.1',
  107. 'SecurityGroup': '1.0',
  108. 'Port': '1.0'}
  109. """
  110. for resource_type, resource_version in versions.items():
  111. self._set_version(consumer, resource_type,
  112. resource_version)
  113. if versions:
  114. self._cleanup_removed_versions(consumer, versions)
  115. else:
  116. self._handle_no_set_versions(consumer)
  117. def _cleanup_removed_versions(self, consumer, versions):
  118. """Check if any version report has been removed, and cleanup."""
  119. prev_resource_types = set(
  120. self._versions_by_consumer[consumer].keys())
  121. cur_resource_types = set(versions.keys())
  122. removed_resource_types = prev_resource_types - cur_resource_types
  123. if removed_resource_types:
  124. LOG.debug("Removing stale tracked versions: %s",
  125. removed_resource_types)
  126. for resource_type in removed_resource_types:
  127. self._set_version(consumer, resource_type, None)
  128. def _handle_no_set_versions(self, consumer):
  129. """Handle consumers reporting no versions."""
  130. if self._versions_by_consumer[consumer]:
  131. self._needs_recalculation = True
  132. LOG.debug("Clearing versions for consumer %s", consumer)
  133. self._versions_by_consumer[consumer] = {}
  134. def get_resource_versions(self, resource_type):
  135. """Fetch the versions necessary to notify all consumers."""
  136. if self._needs_recalculation:
  137. self._recalculate_versions()
  138. self._needs_recalculation = False
  139. return copy.copy(self._versions[resource_type])
  140. def report(self):
  141. """Output debug information about the consumer versions."""
  142. format = lambda versions: pprint.pformat(dict(versions), indent=4)
  143. debug_dict = {'pushed_versions': format(self._versions),
  144. 'consumer_versions': format(self._versions_by_consumer)}
  145. if self.last_report != debug_dict:
  146. self.last_report = debug_dict
  147. LOG.debug('Tracked resource versions report:\n'
  148. 'pushed versions:\n%(pushed_versions)s\n\n'
  149. 'consumer versions:\n%(consumer_versions)s\n',
  150. debug_dict)
  151. # TODO(mangelajo): Add locking if we ever move out of greenthreads.
  152. def _recalculate_versions(self):
  153. """Recalculate the _versions set.
  154. Re-fetch the local (server) versions and expand with consumers'
  155. versions.
  156. """
  157. versions = self._get_local_resource_versions()
  158. for versions_dict in self._versions_by_consumer.values():
  159. for res_type, res_version in versions_dict.items():
  160. versions[res_type].add(res_version)
  161. self._versions = versions
  162. class CachedResourceConsumerTracker(object):
  163. """This class takes care of the caching logic of versions."""
  164. def __init__(self):
  165. # This is TTL expiration time, 0 means it will be expired at start
  166. self._expires_at = 0
  167. self._versions = ResourceConsumerTracker()
  168. def _update_consumer_versions(self):
  169. new_tracker = ResourceConsumerTracker()
  170. neutron_plugin = directory.get_plugin()
  171. agents_db = _import_agents_db()
  172. # If you use RPC callbacks, your plugin needs to implement
  173. # AgentsDbMixin so that we know which resource versions your
  174. # agents consume via RPC, please note that rpc_callbacks are
  175. # only designed to work with agents currently.
  176. if isinstance(neutron_plugin, agents_db.AgentDbMixin):
  177. neutron_plugin.get_agents_resource_versions(new_tracker)
  178. else:
  179. raise exceptions.NoAgentDbMixinImplemented()
  180. # preserve last report state so we don't duplicate logs on refresh
  181. new_tracker.last_report = self._versions.last_report
  182. self._versions = new_tracker
  183. self._versions.report()
  184. def _check_expiration(self):
  185. if time.time() > self._expires_at:
  186. self._update_consumer_versions()
  187. self._expires_at = time.time() + VERSIONS_TTL
  188. def get_resource_versions(self, resource_type):
  189. self._check_expiration()
  190. return self._versions.get_resource_versions(resource_type)
  191. def update_versions(self, consumer, resource_versions):
  192. self._versions.set_versions(consumer, resource_versions)
  193. def report(self):
  194. self._check_expiration()
  195. self._versions.report()
  196. _cached_version_tracker = None
  197. # NOTE(ajo): add locking if we ever stop using greenthreads
  198. def _get_cached_tracker():
  199. global _cached_version_tracker
  200. if not _cached_version_tracker:
  201. _cached_version_tracker = CachedResourceConsumerTracker()
  202. return _cached_version_tracker
  203. def get_resource_versions(resource_type):
  204. """Return the set of versions expected by the consumers of a resource."""
  205. return _get_cached_tracker().get_resource_versions(resource_type)
  206. def update_versions(consumer, resource_versions):
  207. """Update the resources' versions for a consumer id."""
  208. _get_cached_tracker().update_versions(consumer, resource_versions)
  209. def report():
  210. """Report resource versions in debug logs."""
  211. _get_cached_tracker().report()