diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7dfbf878b759..51302ebc6190 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -548,7 +548,7 @@ class ComputeManager(manager.Manager): def reset(self): LOG.info('Reloading compute RPC API') - compute_rpcapi.LAST_VERSION = None + compute_rpcapi.reset_globals() self.compute_rpcapi = compute_rpcapi.ComputeAPI() self.reportclient.clear_provider_cache() diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 96a65dadcbed..5d4c3e7164ac 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -15,6 +15,7 @@ Client side of the compute RPC API. """ +from oslo_concurrency import lockutils from oslo_log import log as logging import oslo_messaging as messaging from oslo_serialization import jsonutils @@ -36,6 +37,18 @@ RPC_TOPIC = "compute" LOG = logging.getLogger(__name__) LAST_VERSION = None NO_COMPUTES_WARNING = False +# Global for ComputeAPI.router. +_ROUTER = None + + +def reset_globals(): + global NO_COMPUTES_WARNING + global LAST_VERSION + global _ROUTER + + NO_COMPUTES_WARNING = False + LAST_VERSION = None + _ROUTER = None def _compute_host(host, instance): @@ -367,25 +380,38 @@ class ComputeAPI(object): 'stein': '5.1', } - def __init__(self): - super(ComputeAPI, self).__init__() - target = messaging.Target(topic=RPC_TOPIC, version='5.0') - upgrade_level = CONF.upgrade_levels.compute - if upgrade_level == 'auto': - version_cap = self._determine_version_cap(target) - else: - version_cap = self.VERSION_ALIASES.get(upgrade_level, - upgrade_level) - serializer = objects_base.NovaObjectSerializer() + @property + def router(self): + """Provides singleton access to nova.rpc.ClientRouter for this API - # NOTE(danms): We need to poke this path to register CONF options - # that we use in self.get_client() - rpc.get_client(target, version_cap, serializer) + The ClientRouter is constructed and accessed as a singleton to avoid + querying all cells for a minimum nova-compute service version when + [upgrade_levels]/compute=auto and we have access to the API DB. + """ + global _ROUTER + if _ROUTER is None: + with lockutils.lock('compute-rpcapi-router'): + if _ROUTER is None: + target = messaging.Target(topic=RPC_TOPIC, version='5.0') + upgrade_level = CONF.upgrade_levels.compute + if upgrade_level == 'auto': + version_cap = self._determine_version_cap(target) + else: + version_cap = self.VERSION_ALIASES.get(upgrade_level, + upgrade_level) + serializer = objects_base.NovaObjectSerializer() - default_client = self.get_client(target, version_cap, serializer) - self.router = rpc.ClientRouter(default_client) + # NOTE(danms): We need to poke this path to register CONF + # options that we use in self.get_client() + rpc.get_client(target, version_cap, serializer) - def _determine_version_cap(self, target): + default_client = self.get_client(target, version_cap, + serializer) + _ROUTER = rpc.ClientRouter(default_client) + return _ROUTER + + @staticmethod + def _determine_version_cap(target): global LAST_VERSION global NO_COMPUTES_WARNING if LAST_VERSION: diff --git a/nova/test.py b/nova/test.py index d32cee482632..32ec4e36ee44 100644 --- a/nova/test.py +++ b/nova/test.py @@ -49,6 +49,7 @@ import six import testtools from nova.compute import resource_tracker +from nova.compute import rpcapi as compute_rpcapi from nova import context from nova.db import api as db from nova import exception @@ -283,6 +284,9 @@ class TestCase(testtools.TestCase): # Reset the global QEMU version flag. images.QEMU_VERSION = None + # Reset the compute RPC API globals (mostly the _ROUTER). + compute_rpcapi.reset_globals() + mox_fixture = self.useFixture(moxstubout.MoxStubout()) self.mox = mox_fixture.mox self.stubs = mox_fixture.stubs diff --git a/nova/tests/unit/compute/test_rpcapi.py b/nova/tests/unit/compute/test_rpcapi.py index 85f0d93d88fb..01961f797751 100644 --- a/nova/tests/unit/compute/test_rpcapi.py +++ b/nova/tests/unit/compute/test_rpcapi.py @@ -79,9 +79,9 @@ class ComputeRpcAPITestCase(test.NoDBTestCase): def test_auto_pin_fails_if_too_old(self, mock_get_min): mock_get_min.return_value = 1955 self.flags(compute='auto', group='upgrade_levels') - compute_rpcapi.LAST_VERSION = None self.assertRaises(exception.ServiceTooOld, - compute_rpcapi.ComputeAPI) + compute_rpcapi.ComputeAPI()._determine_version_cap, + mock.Mock) @mock.patch('nova.objects.service.get_minimum_version_all_cells') def test_auto_pin_with_service_version_zero(self, mock_get_min): @@ -100,8 +100,9 @@ class ComputeRpcAPITestCase(test.NoDBTestCase): mock_get_min.return_value = 1 self.flags(compute='auto', group='upgrade_levels') compute_rpcapi.LAST_VERSION = None - compute_rpcapi.ComputeAPI() - compute_rpcapi.ComputeAPI() + api = compute_rpcapi.ComputeAPI() + for x in range(2): + api._determine_version_cap(mock.Mock()) mock_get_min.assert_called_once_with(mock.ANY, ['nova-compute']) self.assertEqual('4.4', compute_rpcapi.LAST_VERSION) @@ -594,11 +595,9 @@ class ComputeRpcAPITestCase(test.NoDBTestCase): self.flags(connection=None, group='api_database') self.flags(compute='auto', group='upgrade_levels') mock_minver.return_value = 0 - compute_rpcapi.NO_COMPUTES_WARNING = False - compute_rpcapi.LAST_VERSION = None - compute_rpcapi.ComputeAPI() - compute_rpcapi.ComputeAPI() - self.assertEqual(1, mock_log.debug.call_count) + api = compute_rpcapi.ComputeAPI() + for x in range(2): + api._determine_version_cap(mock.Mock()) mock_allcells.assert_not_called() mock_minver.assert_has_calls([ mock.call(mock.ANY, 'nova-compute'), @@ -609,9 +608,8 @@ class ComputeRpcAPITestCase(test.NoDBTestCase): def test_version_cap_all_cells(self, mock_allcells, mock_minver): self.flags(connection='sqlite:///', group='api_database') self.flags(compute='auto', group='upgrade_levels') - compute_rpcapi.LAST_VERSION = None mock_allcells.return_value = 0 - compute_rpcapi.ComputeAPI() + compute_rpcapi.ComputeAPI()._determine_version_cap(mock.Mock()) mock_allcells.assert_called_once_with(mock.ANY, ['nova-compute']) mock_minver.assert_not_called()