diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 9e4e74151e82..00856c3637d4 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -70,6 +70,7 @@ from oslo_utils import uuidutils import prettytable import six.moves.urllib.parse as urlparse +from sqlalchemy.engine import url as sqla_url from nova.api.ec2 import ec2utils from nova import availability_zones @@ -1151,12 +1152,19 @@ class CellV2Commands(object): # based on the database connection url. # The cell0 database will use the same database scheme and # netloc as the main database, with a related path. - scheme, netloc, path, query, fragment = \ - urlparse.urlsplit(CONF.database.connection) - root, ext = os.path.splitext(path) - path = root + "_cell0" + ext - return urlparse.urlunsplit((scheme, netloc, path, query, - fragment)) + connection = CONF.database.connection + # sqlalchemy has a nice utility for parsing database connection + # URLs so we use that here to get the db name so we don't have to + # worry about parsing and splitting a URL which could have special + # characters in the password, which makes parsing a nightmare. + url = sqla_url.make_url(connection) + cell0_db_name = url.database + '_cell0' + # We need to handle multiple occurrences of the substring, e.g. if + # the username and db name are both 'nova' we need to only replace + # the last one, which is the database name in the URL, not the + # username. + connection = connection.rstrip(url.database) + return connection + cell0_db_name dbc = database_connection or cell0_default_connection() ctxt = context.RequestContext() diff --git a/nova/tests/unit/test_nova_manage.py b/nova/tests/unit/test_nova_manage.py index 8d8e3df882f1..d9208280009b 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -15,6 +15,7 @@ import sys +import ddt import fixtures import mock from oslo_db import exception as db_exc @@ -809,6 +810,7 @@ class CellCommandsTestCase(test.NoDBTestCase): mock_db_cell_create.assert_called_once_with(ctxt, exp_values) +@ddt.ddt class CellV2CommandsTestCase(test.NoDBTestCase): USES_DB_SELF = True @@ -1138,6 +1140,29 @@ class CellV2CommandsTestCase(test.NoDBTestCase): self.assertEqual('fake://netloc/nova_cell0', cell_mapping.database_connection) + @ddt.data('mysql+pymysql://nova:abcd0123:AB@controller/nova', + 'mysql+pymysql://nova:abcd0123?AB@controller/nova', + 'mysql+pymysql://nova:abcd0123@AB@controller/nova', + 'mysql+pymysql://nova:abcd0123/AB@controller/nova', + 'mysql+pymysql://test:abcd0123%AB@controller/nova') + def test_map_cell0_default_database_special_characters(self, + decoded_connection): + """Tests that a URL with special characters, like in the credentials, + is handled properly. + """ + self.flags(connection=decoded_connection, group='database') + ctxt = context.RequestContext() + self.commands.map_cell0() + cell_mapping = objects.CellMapping.get_by_uuid( + ctxt, objects.CellMapping.CELL0_UUID) + self.assertEqual('cell0', cell_mapping.name) + self.assertEqual('none:///', cell_mapping.transport_url) + self.assertEqual( + decoded_connection + '_cell0', + cell_mapping.database_connection) + # Delete the cell mapping for the next iteration. + cell_mapping.destroy() + def _test_migrate_simple_command(self, cell0_sync_fail=False): ctxt = context.RequestContext() CONF.set_default('connection', diff --git a/releasenotes/notes/bug-1673613-7357d40ba9ab1fa6.yaml b/releasenotes/notes/bug-1673613-7357d40ba9ab1fa6.yaml new file mode 100644 index 000000000000..cd95df6cc2a9 --- /dev/null +++ b/releasenotes/notes/bug-1673613-7357d40ba9ab1fa6.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Includes the fix for `bug 1673613`_ which could cause issues when upgrading + and running ``nova-manage cell_v2 simple_cell_setup`` or + ``nova-manage cell_v2 map_cell0`` where the database connection is read + from config and has special characters in the URL. + + .. _bug 1673613: https://launchpad.net/bugs/1673613