Restore the ability to load the DB backend lazily

We removed this a while ago, but unfortunately Nova, Ironic and
probably other projects still rely on this feature to prevent import
cycles and postpone initialization of DB backend to the moment when
oslo.config has already parsed the config files (the errors were
hidden when we tested the original commit by the fact that we had
lazy loading of engines in oslo.db code).

This is needed in order to make transitioning of existing projects
to oslo.db as smooth as possible.

Blueprint: oslo-db-lib

Change-Id: I9fe09c132c716345f1cef240483b023e850d4989
This commit is contained in:
Roman Podoliaka 2014-02-12 18:19:17 +02:00
parent dadea37f3e
commit a1a6357370
2 changed files with 32 additions and 10 deletions

View File

@ -22,6 +22,7 @@ API methods.
import functools
import logging
import threading
import time
from openstack.common.db import exception
@ -86,7 +87,8 @@ class wrap_db_retry(object):
class DBAPI(object):
def __init__(self, backend_name, backend_mapping=None, **kwargs):
def __init__(self, backend_name, backend_mapping=None, lazy=False,
**kwargs):
"""Initialize the choosen DB API backend.
:param backend_name: name of the backend to load
@ -95,6 +97,9 @@ class DBAPI(object):
:param backend_mapping: backend name -> module/class to load mapping
:type backend_mapping: dict
:param lazy: load the DB backend lazily on the first DB API method call
:type lazy: bool
Keyword arguments:
:keyword use_db_reconnect: retry DB transactions on disconnect or not
@ -114,14 +119,13 @@ class DBAPI(object):
"""
if backend_mapping is None:
backend_mapping = {}
self._backend = None
self._backend_name = backend_name
self._backend_mapping = backend_mapping or {}
self._lock = threading.Lock()
# Import the untranslated name if we don't have a
# mapping.
backend_path = backend_mapping.get(backend_name, backend_name)
backend_mod = importutils.import_module(backend_path)
self.__backend = backend_mod.get_backend()
if not lazy:
self._load_backend()
self.use_db_reconnect = kwargs.get('use_db_reconnect', False)
self.retry_interval = kwargs.get('retry_interval', 1)
@ -129,9 +133,20 @@ class DBAPI(object):
self.max_retry_interval = kwargs.get('max_retry_interval', 10)
self.max_retries = kwargs.get('max_retries', 20)
def __getattr__(self, key):
attr = getattr(self.__backend, key)
def _load_backend(self):
with self._lock:
if not self._backend:
# Import the untranslated name if we don't have a mapping
backend_path = self._backend_mapping.get(self._backend_name,
self._backend_name)
backend_mod = importutils.import_module(backend_path)
self._backend = backend_mod.get_backend()
def __getattr__(self, key):
if not self._backend:
self._load_backend()
attr = getattr(self._backend, key)
if not hasattr(attr, '__call__'):
return attr
# NOTE(vsergeyev): If `use_db_reconnect` option is set to True, retry

View File

@ -71,6 +71,13 @@ class DBAPITestCase(test_utils.BaseTestCase):
def test_dbapi_unknown_invalid_backend(self):
self.assertRaises(ImportError, api.DBAPI, 'tests.unit.db.not_existent')
def test_dbapi_lazy_loading(self):
dbapi = api.DBAPI('tests.unit.db.test_api', lazy=True)
self.assertIsNone(dbapi._backend)
dbapi.api_class_call1(1, 'abc')
self.assertIsNotNone(dbapi._backend)
class DBReconnectTestCase(DBAPITestCase):
def setUp(self):