Merge "Display full reason for backend not available"
This commit is contained in:
commit
e9a0e9d568
@ -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)
|
||||
|
@ -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:
|
||||
|
83
oslo_db/tests/sqlalchemy/test_fixtures.py
Normal file
83
oslo_db/tests/sqlalchemy/test_fixtures.py
Normal file
@ -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)
|
||||
)
|
@ -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
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user