Handle special characters in database connection URL netloc

When calling "nova-manage cell_v2 simple_cell_setup" or
"nova-manage cell_v2 map_cell0" without passing in the
--database_connection option, we read the [database]/connection
URL from nova.conf, try to split the URL and then create a
default connection based on the name of the original connection,
so if you're cell database's name is 'nova' you'd end up with
'nova_cell0' for the cell0 database name in the URL.

The problem is the database connection URL has credentials in the
netloc and if the password has special characters in it, those can
mess up the URL split, like splitting on ? which is normally denoting
the beginning of the path in a URL.

This change handles special characters in the password by using
a nice DB connection URL parsing utility method available in
sqlalchemy to get the database name out of the connection URL string
so we can replace it properly with the _cell0 suffix.

Adds a release note as this bug causes issues when upgrading.

Conflicts:
      nova/tests/unit/test_nova_manage.py

NOTE(mriedem): The conflict is due to ddt not being used in Newton.

Change-Id: I7a7678e4af8160e6f48b96095154fca6ca48ff09
Closes-Bug: #1673613
(cherry picked from commit 9a9a620ea2)
(cherry picked from commit b501fa7a92)
This commit is contained in:
Matt Riedemann 2017-05-11 18:29:42 -04:00
parent f9a3c3fcff
commit e3076f5ff6
3 changed files with 46 additions and 6 deletions

View File

@ -68,6 +68,7 @@ from oslo_utils import importutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
import six import six
import six.moves.urllib.parse as urlparse import six.moves.urllib.parse as urlparse
from sqlalchemy.engine import url as sqla_url
from nova.api.ec2 import ec2utils from nova.api.ec2 import ec2utils
from nova import availability_zones from nova import availability_zones
@ -1275,12 +1276,19 @@ class CellV2Commands(object):
# based on the database connection url. # based on the database connection url.
# The cell0 database will use the same database scheme and # The cell0 database will use the same database scheme and
# netloc as the main database, with a related path. # netloc as the main database, with a related path.
scheme, netloc, path, query, fragment = \ connection = CONF.database.connection
urlparse.urlsplit(CONF.database.connection) # sqlalchemy has a nice utility for parsing database connection
root, ext = os.path.splitext(path) # URLs so we use that here to get the db name so we don't have to
path = root + "_cell0" + ext # worry about parsing and splitting a URL which could have special
return urlparse.urlunsplit((scheme, netloc, path, query, # characters in the password, which makes parsing a nightmare.
fragment)) 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() dbc = database_connection or cell0_default_connection()
ctxt = context.RequestContext() ctxt = context.RequestContext()

View File

@ -1125,6 +1125,29 @@ class CellV2CommandsTestCase(test.TestCase):
self.assertEqual('fake://netloc/nova_cell0', self.assertEqual('fake://netloc/nova_cell0',
cell_mapping.database_connection) 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): def _test_migrate_simple_command(self, cell0_sync_fail=False):
ctxt = context.RequestContext() ctxt = context.RequestContext()
CONF.set_default('connection', CONF.set_default('connection',

View File

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