from nova import utils
LOG = logging.getLogger(__name__)
+# TODO(melwitt): This cache should be cleared whenever WSGIService receives a
+# SIGHUP and periodically based on an expiration time. Currently, none of the
+# cell caches are purged, so neither is this one, for now.
+CELL_CACHE = {}
class _ContextAuthPlugin(plugin.BaseAuthPlugin):
:param context: The RequestContext to add connection information
:param cell_mapping: A objects.CellMapping object
"""
+ global CELL_CACHE
+
original_db_connection = context.db_connection
# avoid circular import
from nova import db
- db_connection_string = cell_mapping.database_connection
- context.db_connection = db.create_context_manager(db_connection_string)
+
+ # Synchronize access to the cache by multiple API workers.
+ @utils.synchronized(cell_mapping.uuid)
+ def get_or_set_cached_cell_and_set_connections():
+ try:
+ cell_db_conn = CELL_CACHE[cell_mapping.uuid]
+ except KeyError:
+ db_connection_string = cell_mapping.database_connection
+ context.db_connection = db.create_context_manager(
+ db_connection_string)
+ CELL_CACHE[cell_mapping.uuid] = context.db_connection
+ else:
+ context.db_connection = cell_db_conn
+
+ get_or_set_cached_cell_and_set_connections()
+
try:
yield context
finally:
from nova import exception
from nova import objects
from nova import test
+from nova.tests import uuidsentinel as uuids
class ContextTestCase(test.NoDBTestCase):
roles=['admin', 'weasel'])
# Verify the existing db_connection, if any, is restored
ctxt.db_connection = mock.sentinel.db_conn
- mapping = objects.CellMapping(database_connection='fake://')
+ mapping = objects.CellMapping(database_connection='fake://',
+ transport_url='fake://',
+ uuid=uuids.cell)
with context.target_cell(ctxt, mapping):
self.assertEqual(ctxt.db_connection, mock.sentinel.cm)
self.assertEqual(mock.sentinel.db_conn, ctxt.db_connection)
self.assertIsNone(ctxt.user_id)
self.assertIsNone(ctxt.project_id)
self.assertFalse(ctxt.is_admin)
+
+ @mock.patch('nova.db.create_context_manager')
+ def test_target_cell_caching(self, mock_create_cm):
+ mock_create_cm.return_value = mock.sentinel.db_conn_obj
+ ctxt = context.get_context()
+ mapping = objects.CellMapping(database_connection='fake://db',
+ transport_url='fake://mq',
+ uuid=uuids.cell)
+ # First call should create new connection objects.
+ with context.target_cell(ctxt, mapping):
+ self.assertEqual(mock.sentinel.db_conn_obj, ctxt.db_connection)
+ mock_create_cm.assert_called_once_with('fake://db')
+ # Second call should use cached objects.
+ mock_create_cm.reset_mock()
+ with context.target_cell(ctxt, mapping):
+ self.assertEqual(mock.sentinel.db_conn_obj, ctxt.db_connection)
+ mock_create_cm.assert_not_called()
--- /dev/null
+---
+fixes:
+ - |
+ Fixes `bug 1691545`_ in which there was a significant increase in database
+ connections because of the way connections to cell databases were being
+ established. With this fix, objects related to database connections are
+ cached in the API service and reused to prevent new connections being
+ established for every communication with cell databases.
+
+ .. _bug 1691545: https://bugs.launchpad.net/nova/+bug/1691545