From 2166490a058c653a726372415cf0df03579e5542 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 24 May 2017 17:53:14 -0400 Subject: Warn on URL without a drivername Older installations may still be running with URLs of the form "mysql://user:pass@host/dbname", which defaults to the native MySQL driver that is not supported by eventlet. Warn that URLs should be qualified with an explicit driver, and in the case of MySQL that PyMySQL should be used. Change-Id: Ie973a43c8d056778d02703cf75bfc52a90027dea --- oslo_db/sqlalchemy/engines.py | 20 +++++++++++++ oslo_db/tests/sqlalchemy/test_sqlalchemy.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/oslo_db/sqlalchemy/engines.py b/oslo_db/sqlalchemy/engines.py index 601be76..a9ac662 100644 --- a/oslo_db/sqlalchemy/engines.py +++ b/oslo_db/sqlalchemy/engines.py @@ -100,6 +100,24 @@ def _setup_logging(connection_debug=0): logger.setLevel(logging.WARNING) +def _vet_url(url): + if "+" not in url.drivername and not url.drivername.startswith("sqlite"): + if url.drivername.startswith("mysql"): + LOG.warning( + "URL %r does not contain a '+drivername' portion, " + "and will make use of a default driver. " + "A full dbname+drivername:// protocol is recommended. " + "For MySQL, it is strongly recommended that mysql+pymysql:// " + "be specified for maximum service compatibility", url + ) + else: + LOG.warning( + "URL %r does not contain a '+drivername' portion, " + "and will make use of a default driver. " + "A full dbname+drivername:// protocol is recommended.", url + ) + + def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, idle_timeout=3600, connection_debug=0, max_pool_size=None, max_overflow=None, @@ -112,6 +130,8 @@ def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, url = sqlalchemy.engine.url.make_url(sql_connection) + _vet_url(url) + engine_args = { "pool_recycle": idle_timeout, 'convert_unicode': True, diff --git a/oslo_db/tests/sqlalchemy/test_sqlalchemy.py b/oslo_db/tests/sqlalchemy/test_sqlalchemy.py index a37cdc6..bcbad45 100644 --- a/oslo_db/tests/sqlalchemy/test_sqlalchemy.py +++ b/oslo_db/tests/sqlalchemy/test_sqlalchemy.py @@ -664,6 +664,52 @@ class CreateEngineTest(oslo_test.BaseTestCase): engines._thread_yield ) + def test_warn_on_missing_driver(self): + + warnings = mock.Mock() + + def warn_interpolate(msg, args): + # test the interpolation itself to ensure the password + # is concealed + warnings.warning(msg % args) + + with mock.patch( + "oslo_db.sqlalchemy.engines.LOG.warning", + warn_interpolate): + + engines._vet_url( + url.make_url("mysql://scott:tiger@some_host/some_db")) + engines._vet_url(url.make_url( + "mysql+mysqldb://scott:tiger@some_host/some_db")) + engines._vet_url(url.make_url( + "mysql+pymysql://scott:tiger@some_host/some_db")) + engines._vet_url(url.make_url( + "postgresql+psycopg2://scott:tiger@some_host/some_db")) + engines._vet_url(url.make_url( + "postgresql://scott:tiger@some_host/some_db")) + + self.assertEqual( + [ + mock.call.warning( + "URL mysql://scott:***@some_host/some_db does not contain " + "a '+drivername' portion, " + "and will make use of a default driver. " + "A full dbname+drivername:// protocol is recommended. " + "For MySQL, it is strongly recommended that " + "mysql+pymysql:// " + "be specified for maximum service compatibility", + + ), + mock.call.warning( + "URL postgresql://scott:***@some_host/some_db does not " + "contain a '+drivername' portion, " + "and will make use of a default driver. " + "A full dbname+drivername:// protocol is recommended." + ) + ], + warnings.mock_calls + ) + class ProcessGuardTest(test_base.DbTestCase): def test_process_guard(self): -- cgit v1.2.1