Add a specific exception for 'unknown database' errors

Add an exception filter to distinguish 'unknown database' errors from
other DBAPIError's. This effectively means we no longer
unconditionally print a traceback to logs, when someone tries to
connect to a database, that does not exist, but just raise a specific
exception instead and let the caller handle it.

Closes-Bug: #1631033

Change-Id: Iebc1ec1e8153917fbcde116d04bc171272993bbd
This commit is contained in:
Roman Podoliaka
2016-10-10 15:13:19 +03:00
parent 2f1944d24a
commit 08d5f3bc02
3 changed files with 94 additions and 0 deletions

View File

@@ -155,6 +155,18 @@ class DBNonExistentTable(DBError):
super(DBNonExistentTable, self).__init__(inner_exception)
class DBNonExistentDatabase(DBError):
"""Database does not exist.
:param database: database name
:type database: str
"""
def __init__(self, database, inner_exception=None):
self.database = database
super(DBNonExistentDatabase, self).__init__(inner_exception)
class DBDeadlock(DBError):
"""Database dead lock error.

View File

@@ -285,6 +285,24 @@ def _check_table_non_existing(
raise exception.DBNonExistentTable(match.group("table"), programming_error)
@filters("mysql", sqla_exc.InternalError,
r".*1049,.*Unknown database '(?P<database>.+)'\"")
@filters("mysql", sqla_exc.OperationalError,
r".*1049,.*Unknown database '(?P<database>.+)'\"")
@filters("postgresql", sqla_exc.OperationalError,
r".*database \"(?P<database>.+)\" does not exist")
@filters("sqlite", sqla_exc.OperationalError,
".*unable to open database file.*")
def _check_database_non_existing(
error, match, engine_name, is_disconnect):
try:
database = match.group("database")
except IndexError:
database = None
raise exception.DBNonExistentDatabase(database, error)
@filters("ibm_db_sa", sqla_exc.IntegrityError, r"^.*SQL0803N.*$")
def _db2_dupe_key_error(integrity_error, match, engine_name, is_disconnect):
"""Filter for DB2 duplicate key errors.

View File

@@ -21,6 +21,7 @@ import mock
from oslotest import base as oslo_test_base
import six
import sqlalchemy as sqla
from sqlalchemy.engine import url as sqla_url
from sqlalchemy import event
import sqlalchemy.exc
from sqlalchemy.ext.declarative import declarative_base
@@ -379,6 +380,69 @@ class TestNonExistentTableMySQL(
self.assertEqual("foo", matched.table)
class TestNonExistentDatabase(
_SQLAExceptionMatcher,
test_base.DbTestCase):
def setUp(self):
super(TestNonExistentDatabase, self).setUp()
url = sqla_url.make_url(str(self.engine.url))
url.database = 'non_existent_database'
self.url = url
def test_raise(self):
matched = self.assertRaises(
exception.DBNonExistentDatabase,
engines.create_engine,
sqla_url.make_url(
'sqlite:////non_existent_dir/non_existent_database')
)
self.assertIsNone(matched.database)
self.assertInnerException(
matched,
sqlalchemy.exc.OperationalError,
'unable to open database file',
)
class TestNonExistentDatabaseMySQL(
TestNonExistentDatabase,
test_base.MySQLOpportunisticTestCase):
def test_raise(self):
matched = self.assertRaises(
exception.DBNonExistentDatabase,
engines.create_engine,
self.url
)
self.assertEqual('non_existent_database', matched.database)
# NOTE(rpodolyaka) cannot check precisely with assertInnerException
# since MySQL errors are not the same depending on its version
self.assertIsInstance(
matched.inner_exception,
(sqlalchemy.exc.InternalError, sqlalchemy.exc.OperationalError),
)
class TestNonExistentDatabasePostgreSQL(
TestNonExistentDatabase,
test_base.PostgreSQLOpportunisticTestCase):
def test_raise(self):
matched = self.assertRaises(
exception.DBNonExistentDatabase,
engines.create_engine,
self.url
)
self.assertEqual('non_existent_database', matched.database)
self.assertInnerException(
matched,
sqlalchemy.exc.OperationalError,
'fatal: database "non_existent_database" does not exist\n',
)
class TestReferenceErrorSQLite(_SQLAExceptionMatcher, test_base.DbTestCase):
def setUp(self):