summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-08-03 17:20:49 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2016-08-08 14:25:58 -0400
commitaf27831a9e6eceda192f63c1368d0aa1cc70c17e (patch)
tree0bf9e76de249be8099d1c3fcbbe9910c697a99a4
parent428dbe1eec5a77d70af192b7eccaff89495940db (diff)
downloadoslo-db-af27831a9e6eceda192f63c1368d0aa1cc70c17e.tar.gz
Display full reason for backend not available
This adds to the provisioning system the ability to preserve and re-display the full reason for a particular DB backend not available, and propagates this all the way through the skip() emitted by the OpportunisticTestCase and DbFixture. As part of the change, the database type requested is added to the messages themselves, which also appears in the messages emitted by the consuming is_backend_avail() function, which has tests that check for these explicit messages. New tests specific to the Backend object are added which are not dependent on is_backend_avail(); as this function is deprecated, we will be able to remove its tests at the same time as the function itself with no loss in coverage. Change-Id: I7c00e0770b02aa751e184edfecdec4306de6340c
-rw-r--r--oslo_db/sqlalchemy/provision.py20
-rw-r--r--oslo_db/sqlalchemy/test_base.py21
-rw-r--r--oslo_db/tests/sqlalchemy/test_fixtures.py83
-rw-r--r--oslo_db/tests/sqlalchemy/test_provision.py58
-rw-r--r--oslo_db/tests/sqlalchemy/test_utils.py8
5 files changed, 176 insertions, 14 deletions
diff --git a/oslo_db/sqlalchemy/provision.py b/oslo_db/sqlalchemy/provision.py
index ff6ebc0..7a1eaee 100644
--- a/oslo_db/sqlalchemy/provision.py
+++ b/oslo_db/sqlalchemy/provision.py
@@ -157,7 +157,6 @@ class Backend(object):
self.engine = None
self.impl = BackendImpl.impl(database_type)
self.current_dbs = set()
- Backend.backends_by_database_type[database_type] = self
@classmethod
def backend_for_database_type(cls, database_type):
@@ -167,7 +166,8 @@ class Backend(object):
try:
backend = cls.backends_by_database_type[database_type]
except KeyError:
- raise exception.BackendNotAvailable(database_type)
+ raise exception.BackendNotAvailable(
+ "Backend '%s' is unavailable: No such backend" % database_type)
else:
return backend._verify()
@@ -197,14 +197,15 @@ class Backend(object):
if not self.verified:
try:
eng = self._ensure_backend_available(self.url)
- except exception.BackendNotAvailable:
+ except exception.BackendNotAvailable as bne:
+ self._no_engine_reason = str(bne)
raise
else:
self.engine = eng
finally:
self.verified = True
if self.engine is None:
- raise exception.BackendNotAvailable(self.database_type)
+ raise exception.BackendNotAvailable(self._no_engine_reason)
return self
@classmethod
@@ -219,7 +220,9 @@ class Backend(object):
LOG.info(
_LI("The %(dbapi)s backend is unavailable: %(err)s"),
dict(dbapi=url.drivername, err=i_e))
- raise exception.BackendNotAvailable("No DBAPI installed")
+ raise exception.BackendNotAvailable(
+ "Backend '%s' is unavailable: No DBAPI installed" %
+ url.drivername)
else:
try:
conn = eng.connect()
@@ -231,7 +234,9 @@ class Backend(object):
_LI("The %(dbapi)s backend is unavailable: %(err)s"),
dict(dbapi=url.drivername, err=d_e)
)
- raise exception.BackendNotAvailable("Could not connect")
+ raise exception.BackendNotAvailable(
+ "Backend '%s' is unavailable: Could not connect" %
+ url.drivername)
else:
conn.close()
return eng
@@ -312,7 +317,8 @@ class Backend(object):
url = sa_url.make_url(url_str)
m = re.match(r'([^+]+?)(?:\+(.+))?$', url.drivername)
database_type = m.group(1)
- Backend(database_type, url)
+ Backend.backends_by_database_type[database_type] = \
+ Backend(database_type, url)
@six.add_metaclass(abc.ABCMeta)
diff --git a/oslo_db/sqlalchemy/test_base.py b/oslo_db/sqlalchemy/test_base.py
index 40b9c30..f25d266 100644
--- a/oslo_db/sqlalchemy/test_base.py
+++ b/oslo_db/sqlalchemy/test_base.py
@@ -65,9 +65,10 @@ class DbFixture(fixtures.Fixture):
testresources.tearDownResources,
self.test, self.test.resources, testresources._get_result()
)
- if not hasattr(self.test, 'db'):
- msg = "backend '%s' unavailable" % self.DRIVER
- if self.skip_on_unavailable_db:
+
+ if not self.test._has_db_resource():
+ msg = self.test._get_db_resource_not_available_reason()
+ if self.test.SKIP_ON_UNAVAILABLE_DB:
self.test.skip(msg)
else:
self.test.fail(msg)
@@ -98,9 +99,17 @@ class DbTestCase(test_base.BaseTestCase):
SCHEMA_SCOPE = None
SKIP_ON_UNAVAILABLE_DB = True
+ _db_not_available = {}
_schema_resources = {}
_database_resources = {}
+ def _get_db_resource_not_available_reason(self):
+ return self._db_not_available.get(self.FIXTURE.DRIVER, None)
+
+ def _has_db_resource(self):
+ return self._database_resources.get(
+ self.FIXTURE.DRIVER, None) is not None
+
def _resources_for_driver(self, driver, schema_scope, generate_schema):
# testresources relies on the identity and state of the
# TestResourceManager objects in play to correctly manage
@@ -110,12 +119,14 @@ class DbTestCase(test_base.BaseTestCase):
# so we have to code the TestResourceManager logic into the
# .resources attribute and ensure that the same set of test
# variables always produces the same TestResourceManager objects.
+
if driver not in self._database_resources:
try:
self._database_resources[driver] = \
provision.DatabaseResource(driver)
- except exception.BackendNotAvailable:
+ except exception.BackendNotAvailable as bne:
self._database_resources[driver] = None
+ self._db_not_available[driver] = str(bne)
database_resource = self._database_resources[driver]
if database_resource is None:
@@ -200,7 +211,7 @@ def backend_specific(*dialects):
if self.engine.name not in dialects:
msg = ('The test "%s" can be run '
'only on %s. Current engine is %s.')
- args = (reflection.get_callable_name(f), ' '.join(dialects),
+ args = (reflection.get_callable_name(f), ', '.join(dialects),
self.engine.name)
self.skip(msg % args)
else:
diff --git a/oslo_db/tests/sqlalchemy/test_fixtures.py b/oslo_db/tests/sqlalchemy/test_fixtures.py
new file mode 100644
index 0000000..5d63d69
--- /dev/null
+++ b/oslo_db/tests/sqlalchemy/test_fixtures.py
@@ -0,0 +1,83 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from oslo_db.sqlalchemy import provision
+from oslo_db.sqlalchemy import test_base
+from oslotest import base as oslo_test_base
+
+
+class BackendSkipTest(oslo_test_base.BaseTestCase):
+
+ def test_skip_no_dbapi(self):
+
+ class FakeDatabaseOpportunisticFixture(test_base.DbFixture):
+ DRIVER = 'postgresql'
+
+ class SomeTest(test_base.DbTestCase):
+ FIXTURE = FakeDatabaseOpportunisticFixture
+
+ def runTest(self):
+ pass
+
+ st = SomeTest()
+
+ # patch in replacement lookup dictionaries to avoid
+ # leaking from/to other tests
+ with mock.patch(
+ "oslo_db.sqlalchemy.provision."
+ "Backend.backends_by_database_type", {
+ "postgresql":
+ provision.Backend("postgresql", "postgresql://")}):
+ st._database_resources = {}
+ st._db_not_available = {}
+ st._schema_resources = {}
+
+ with mock.patch(
+ "sqlalchemy.create_engine",
+ mock.Mock(side_effect=ImportError())):
+
+ self.assertEqual([], st.resources)
+
+ ex = self.assertRaises(
+ self.skipException,
+ st.setUp
+ )
+
+ self.assertEqual(
+ "Backend 'postgresql' is unavailable: No DBAPI installed",
+ str(ex)
+ )
+
+ def test_skip_no_such_backend(self):
+
+ class FakeDatabaseOpportunisticFixture(test_base.DbFixture):
+ DRIVER = 'postgresql+nosuchdbapi'
+
+ class SomeTest(test_base.DbTestCase):
+ FIXTURE = FakeDatabaseOpportunisticFixture
+
+ def runTest(self):
+ pass
+
+ st = SomeTest()
+
+ ex = self.assertRaises(
+ self.skipException,
+ st.setUp
+ )
+
+ self.assertEqual(
+ "Backend 'postgresql+nosuchdbapi' is unavailable: No such backend",
+ str(ex)
+ )
diff --git a/oslo_db/tests/sqlalchemy/test_provision.py b/oslo_db/tests/sqlalchemy/test_provision.py
index 210e180..1e9475f 100644
--- a/oslo_db/tests/sqlalchemy/test_provision.py
+++ b/oslo_db/tests/sqlalchemy/test_provision.py
@@ -10,7 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import mock
from oslotest import base as oslo_test_base
+from sqlalchemy import exc as sa_exc
from sqlalchemy import inspect
from sqlalchemy import schema
from sqlalchemy import types
@@ -73,6 +75,62 @@ class DropAllObjectsTest(test_base.DbTestCase):
)
+class BackendNotAvailableTest(oslo_test_base.BaseTestCase):
+ def test_no_dbapi(self):
+ backend = provision.Backend(
+ "postgresql", "postgresql+nosuchdbapi://hostname/dsn")
+
+ with mock.patch(
+ "sqlalchemy.create_engine",
+ mock.Mock(side_effect=ImportError("nosuchdbapi"))):
+
+ # NOTE(zzzeek): Call and test the _verify function twice, as it
+ # exercises a different code path on subsequent runs vs.
+ # the first run
+ ex = self.assertRaises(
+ exception.BackendNotAvailable,
+ backend._verify)
+ self.assertEqual(
+ "Backend 'postgresql+nosuchdbapi' is unavailable: "
+ "No DBAPI installed", str(ex))
+
+ ex = self.assertRaises(
+ exception.BackendNotAvailable,
+ backend._verify)
+ self.assertEqual(
+ "Backend 'postgresql+nosuchdbapi' is unavailable: "
+ "No DBAPI installed", str(ex))
+
+ def test_cant_connect(self):
+ backend = provision.Backend(
+ "postgresql", "postgresql+nosuchdbapi://hostname/dsn")
+
+ with mock.patch(
+ "sqlalchemy.create_engine",
+ mock.Mock(return_value=mock.Mock(connect=mock.Mock(
+ side_effect=sa_exc.OperationalError(
+ "can't connect", None, None))
+ ))
+ ):
+
+ # NOTE(zzzeek): Call and test the _verify function twice, as it
+ # exercises a different code path on subsequent runs vs.
+ # the first run
+ ex = self.assertRaises(
+ exception.BackendNotAvailable,
+ backend._verify)
+ self.assertEqual(
+ "Backend 'postgresql+nosuchdbapi' is unavailable: "
+ "Could not connect", str(ex))
+
+ ex = self.assertRaises(
+ exception.BackendNotAvailable,
+ backend._verify)
+ self.assertEqual(
+ "Backend 'postgresql+nosuchdbapi' is unavailable: "
+ "Could not connect", str(ex))
+
+
class MySQLDropAllObjectsTest(
DropAllObjectsTest, test_base.MySQLOpportunisticTestCase):
pass
diff --git a/oslo_db/tests/sqlalchemy/test_utils.py b/oslo_db/tests/sqlalchemy/test_utils.py
index 64f8ef1..0de123e 100644
--- a/oslo_db/tests/sqlalchemy/test_utils.py
+++ b/oslo_db/tests/sqlalchemy/test_utils.py
@@ -785,7 +785,9 @@ class TestConnectionUtils(test_utils.BaseTestCase):
exception.BackendNotAvailable,
provision.Backend._ensure_backend_available, self.connect_string
)
- self.assertEqual("Could not connect", str(exc))
+ self.assertEqual(
+ "Backend 'postgresql' is unavailable: "
+ "Could not connect", str(exc))
self.assertEqual(
"The postgresql backend is unavailable: %s" % err,
log.output.strip())
@@ -802,7 +804,9 @@ class TestConnectionUtils(test_utils.BaseTestCase):
exception.BackendNotAvailable,
provision.Backend._ensure_backend_available, self.connect_string
)
- self.assertEqual("No DBAPI installed", str(exc))
+ self.assertEqual(
+ "Backend 'postgresql' is unavailable: "
+ "No DBAPI installed", str(exc))
self.assertEqual(
"The postgresql backend is unavailable: Can't import "
"DBAPI module foobar", log.output.strip())