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:
Matt Riedemann 2019-04-01 17:41:15 -04:00
parent 137f5f294c
commit 7d54f91e9f
4 changed files with 56 additions and 28 deletions

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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()