diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 57b68a2849e5..2b0cf37f973a 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -68,6 +68,7 @@ from oslo_utils import importutils from oslo_utils import uuidutils import six 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 @@ -1275,12 +1276,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 126835456ec5..fc222f03970a 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -1125,6 +1125,29 @@ class CellV2CommandsTestCase(test.TestCase): self.assertEqual('fake://netloc/nova_cell0', cell_mapping.database_connection) + def test_map_cell0_default_database_special_characters(self): + """Tests that a URL with special characters, like in the credentials, + is handled properly. + """ + for decoded_connection in ( + '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'): + 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