Make nova.compute.rpcapi.ComputeAPI.router a singleton
When starting nova-api before any nova-computes are started
and registered in the cell DBs, and with
[upgrade_levels]/compute=auto, the compute RPC API client
construction will iterate all cells looking for a minimum
nova-compute service version, not find one, and thus not
cache the result in the LAST_VERSION global.
There are 30+ API controller classes that construct an
instance of nova.compute.api.API which itself constructs
a nova.compute.rpcapi.ComputeAPI object which determines
the version cap as described above, and that is per API
worker. Each cell DB call goes through RequestContext.set_target_cell
which has a lock in it, so in this scenario on start of
nova-api there can be a lot of locking log messages for
get_or_set_cached_cell_and_set_connections.
The RPC API ClientRouter can be a singleton and just constructed
on first access to avoid the redundant database queries which
is what this change does.
To preserve the LAST_VERSION re-calculation that was in
ComputeManager.reset(), we have to also reset the _ROUTER global
so ComputeManager.reset() now resets all of the compute RPC API
globals.
Change-Id: I48109d5e32a2e9635c240da1c77f7f6cc7e3c76d
Related-Bug: #1807219
Related-Bug: #1815697
(cherry picked from commit ae659668b5
)
This commit is contained in:
parent
137f5f294c
commit
7d54f91e9f
|
@ -548,7 +548,7 @@ class ComputeManager(manager.Manager):
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
LOG.info('Reloading compute RPC API')
|
LOG.info('Reloading compute RPC API')
|
||||||
compute_rpcapi.LAST_VERSION = None
|
compute_rpcapi.reset_globals()
|
||||||
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
|
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
|
||||||
self.reportclient.clear_provider_cache()
|
self.reportclient.clear_provider_cache()
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
Client side of the compute RPC API.
|
Client side of the compute RPC API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from oslo_concurrency import lockutils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
@ -36,6 +37,18 @@ RPC_TOPIC = "compute"
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
LAST_VERSION = None
|
LAST_VERSION = None
|
||||||
NO_COMPUTES_WARNING = False
|
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):
|
def _compute_host(host, instance):
|
||||||
|
@ -367,25 +380,38 @@ class ComputeAPI(object):
|
||||||
'stein': '5.1',
|
'stein': '5.1',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
@property
|
||||||
super(ComputeAPI, self).__init__()
|
def router(self):
|
||||||
target = messaging.Target(topic=RPC_TOPIC, version='5.0')
|
"""Provides singleton access to nova.rpc.ClientRouter for this API
|
||||||
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()
|
|
||||||
|
|
||||||
# NOTE(danms): We need to poke this path to register CONF options
|
The ClientRouter is constructed and accessed as a singleton to avoid
|
||||||
# that we use in self.get_client()
|
querying all cells for a minimum nova-compute service version when
|
||||||
rpc.get_client(target, version_cap, serializer)
|
[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)
|
# NOTE(danms): We need to poke this path to register CONF
|
||||||
self.router = rpc.ClientRouter(default_client)
|
# 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 LAST_VERSION
|
||||||
global NO_COMPUTES_WARNING
|
global NO_COMPUTES_WARNING
|
||||||
if LAST_VERSION:
|
if LAST_VERSION:
|
||||||
|
|
|
@ -49,6 +49,7 @@ import six
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
from nova.compute import resource_tracker
|
from nova.compute import resource_tracker
|
||||||
|
from nova.compute import rpcapi as compute_rpcapi
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova.db import api as db
|
from nova.db import api as db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
@ -283,6 +284,9 @@ class TestCase(testtools.TestCase):
|
||||||
# Reset the global QEMU version flag.
|
# Reset the global QEMU version flag.
|
||||||
images.QEMU_VERSION = None
|
images.QEMU_VERSION = None
|
||||||
|
|
||||||
|
# Reset the compute RPC API globals (mostly the _ROUTER).
|
||||||
|
compute_rpcapi.reset_globals()
|
||||||
|
|
||||||
mox_fixture = self.useFixture(moxstubout.MoxStubout())
|
mox_fixture = self.useFixture(moxstubout.MoxStubout())
|
||||||
self.mox = mox_fixture.mox
|
self.mox = mox_fixture.mox
|
||||||
self.stubs = mox_fixture.stubs
|
self.stubs = mox_fixture.stubs
|
||||||
|
|
|
@ -79,9 +79,9 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
|
||||||
def test_auto_pin_fails_if_too_old(self, mock_get_min):
|
def test_auto_pin_fails_if_too_old(self, mock_get_min):
|
||||||
mock_get_min.return_value = 1955
|
mock_get_min.return_value = 1955
|
||||||
self.flags(compute='auto', group='upgrade_levels')
|
self.flags(compute='auto', group='upgrade_levels')
|
||||||
compute_rpcapi.LAST_VERSION = None
|
|
||||||
self.assertRaises(exception.ServiceTooOld,
|
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')
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
|
||||||
def test_auto_pin_with_service_version_zero(self, mock_get_min):
|
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
|
mock_get_min.return_value = 1
|
||||||
self.flags(compute='auto', group='upgrade_levels')
|
self.flags(compute='auto', group='upgrade_levels')
|
||||||
compute_rpcapi.LAST_VERSION = None
|
compute_rpcapi.LAST_VERSION = None
|
||||||
compute_rpcapi.ComputeAPI()
|
api = compute_rpcapi.ComputeAPI()
|
||||||
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'])
|
mock_get_min.assert_called_once_with(mock.ANY, ['nova-compute'])
|
||||||
self.assertEqual('4.4', compute_rpcapi.LAST_VERSION)
|
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(connection=None, group='api_database')
|
||||||
self.flags(compute='auto', group='upgrade_levels')
|
self.flags(compute='auto', group='upgrade_levels')
|
||||||
mock_minver.return_value = 0
|
mock_minver.return_value = 0
|
||||||
compute_rpcapi.NO_COMPUTES_WARNING = False
|
api = compute_rpcapi.ComputeAPI()
|
||||||
compute_rpcapi.LAST_VERSION = None
|
for x in range(2):
|
||||||
compute_rpcapi.ComputeAPI()
|
api._determine_version_cap(mock.Mock())
|
||||||
compute_rpcapi.ComputeAPI()
|
|
||||||
self.assertEqual(1, mock_log.debug.call_count)
|
|
||||||
mock_allcells.assert_not_called()
|
mock_allcells.assert_not_called()
|
||||||
mock_minver.assert_has_calls([
|
mock_minver.assert_has_calls([
|
||||||
mock.call(mock.ANY, 'nova-compute'),
|
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):
|
def test_version_cap_all_cells(self, mock_allcells, mock_minver):
|
||||||
self.flags(connection='sqlite:///', group='api_database')
|
self.flags(connection='sqlite:///', group='api_database')
|
||||||
self.flags(compute='auto', group='upgrade_levels')
|
self.flags(compute='auto', group='upgrade_levels')
|
||||||
compute_rpcapi.LAST_VERSION = None
|
|
||||||
mock_allcells.return_value = 0
|
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_allcells.assert_called_once_with(mock.ANY, ['nova-compute'])
|
||||||
mock_minver.assert_not_called()
|
mock_minver.assert_not_called()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue