Make CellDatabases fixture work over RPC

This makes the CellDatabases fixture work when we do the (soon to be
very common) cell-targeted rpc call pattern. Right now, we target
a cell, which sets context.db_connection, and then make the RPC call.
The RPC call serializes and then deserializes the context, which
means when we start running the code on the other side of the (fake)
RPC call, we no longer have the target set. This is fine in real life
because we want to make a call to compute and have it use its
main/default DB config for talking to its local cell. However, in
things like functional tests where we are all in a single process,
use fake RPC, and can't have that separation from global config and
database api layout, we need to preserve the cell targeting to
be able to test behavior equivalent to reality.

Change-Id: If02f5530b7a1ad725a6122d6bd3d309d6d5071f3
This commit is contained in:
Dan Smith
2017-02-22 10:33:47 -08:00
parent 486b90b24e
commit 6a0f091d9a
+58
View File
@@ -330,6 +330,32 @@ class SingleCellSimple(fixtures.Fixture):
yield context
class CheatingSerializer(rpc.RequestContextSerializer):
"""A messaging.RequestContextSerializer that helps with cells.
Our normal serializer does not pass in the context like db_connection
and mq_connection, for good reason. We don't really want/need to
force a remote RPC server to use our values for this. However,
during unit and functional tests, since we're all in the same
process, we want cell-targeted RPC calls to preserve these values.
Unless we had per-service config and database layer state for
the fake services we start, this is a reasonable cheat.
"""
def serialize_context(self, context):
"""Serialize context with the db_connection inside."""
values = super(CheatingSerializer, self).serialize_context(context)
values['db_connection'] = context.db_connection
values['mq_connection'] = context.mq_connection
return values
def deserialize_context(self, values):
"""Deserialize context and honor db_connection if present."""
ctxt = super(CheatingSerializer, self).deserialize_context(values)
ctxt.db_connection = values.pop('db_connection', None)
ctxt.mq_connection = values.pop('mq_connection', None)
return ctxt
class CellDatabases(fixtures.Fixture):
"""Create per-cell databases for testing.
@@ -389,11 +415,36 @@ class CellDatabases(fixtures.Fixture):
return ctxt_mgr
def _wrap_get_context_manager(self, context):
try:
# If already targeted, we can proceed without a lock
if context.db_connection:
return context.db_connection
except AttributeError:
# Unit tests with None, FakeContext, etc
pass
# NOTE(melwitt): This is a hack to try to deal with
# local accesses i.e. non target_cell accesses.
with self._cell_lock.read_lock():
return self._last_ctxt_mgr
def _wrap_get_server(self, target, endpoints, serializer=None):
"""Mirror rpc.get_server() but with our special sauce."""
serializer = CheatingSerializer(serializer)
return messaging.get_rpc_server(rpc.TRANSPORT,
target,
endpoints,
executor='eventlet',
serializer=serializer)
def _wrap_get_client(self, target, version_cap=None, serializer=None):
"""Mirror rpc.get_client() but with our special sauce."""
serializer = CheatingSerializer(serializer)
return messaging.RPCClient(rpc.TRANSPORT,
target,
version_cap=version_cap,
serializer=serializer)
def add_cell_database(self, connection_str, default=False):
"""Add a cell database to the fixture.
@@ -454,6 +505,13 @@ class CellDatabases(fixtures.Fixture):
'nova.context.target_cell',
self._wrap_target_cell))
self.useFixture(fixtures.MonkeyPatch(
'nova.rpc.get_server',
self._wrap_get_server))
self.useFixture(fixtures.MonkeyPatch(
'nova.rpc.get_client',
self._wrap_get_client))
def cleanup(self):
for ctxt_mgr in self._ctxt_mgrs.values():
engine = ctxt_mgr.get_legacy_facade().get_engine()