Merge "Display full reason for backend not available"

This commit is contained in:
Jenkins 2016-08-11 12:52:34 +00:00 committed by Gerrit Code Review
commit e9a0e9d568
5 changed files with 176 additions and 14 deletions

View File

@ -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)

View File

@ -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:

View 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)
)

View File

@ -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

View File

@ -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())