From 6a0f091d9afc83e7bd94b0515108ac3a664367b0 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 15 Feb 2017 16:13:06 -0800 Subject: [PATCH] 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 --- nova/tests/fixtures.py | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/nova/tests/fixtures.py b/nova/tests/fixtures.py index a58327dd68..53f6a04cef 100644 --- a/nova/tests/fixtures.py +++ b/nova/tests/fixtures.py @@ -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()