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):
|
||||
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()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue