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.

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

View File

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

View File

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

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