Fix secure method to work with PXC

When PXC was implemented, the secure method was overridden as the
stock one was causing problems on prepare. This issue seems to
be caused by the order of work done in the call. If the admin
user is set up first and then used to delete the anon users,
everything works for all flavors of MySQL.

Tested using the scenario tests on MySQL, Percona, PXC and MariaDB.

Also cleaned up some of the common code and made the connection URL
more resilient to special password characters. Updated the 'supported'
tests and fixed the user actions one to work with PXC.

Change-Id: Idf9a096b8870e428090454528b79930f3d2fd760
Closes-Bug: #1585355
This commit is contained in:
Peter Stachowski 2016-05-24 16:43:15 -04:00
parent ff49045744
commit 0eb332ae8a
7 changed files with 36 additions and 78 deletions

View File

@ -15,12 +15,8 @@
#
from oslo_log import log as logging
import sqlalchemy
from sqlalchemy.sql.expression import text
from trove.common import cfg
from trove.common.i18n import _
from trove.common import utils as utils
from trove.guestagent.datastore.galera_common import service as galera_service
from trove.guestagent.datastore.mysql_common import service as mysql_service
@ -52,43 +48,6 @@ class PXCApp(galera_service.GaleraApp):
def cluster_configuration(self):
return self.configuration_manager.get_value('mysqld')
def secure(self, config_contents):
LOG.info(_("Generating admin password."))
admin_password = utils.generate_random_password()
mysql_service.clear_expired_password()
uri = "mysql+pymysql://root:@localhost:3306"
engine = sqlalchemy.create_engine(uri, echo=True)
with self.local_sql_client(engine) as client:
self._remove_anonymous_user(client)
self._create_admin_user(client, admin_password)
self.stop_db()
self._reset_configuration(config_contents, admin_password)
self.start_mysql()
# TODO(cp16net) figure out reason for PXC not updating the password
try:
with self.local_sql_client(engine) as client:
query = text("select Host, User from mysql.user;")
client.execute(query)
except Exception:
LOG.debug('failed to query mysql')
# creating the admin user after the config files are written because
# pxc was not committing the grant for the admin user after removing
# the annon users.
self._wait_for_mysql_to_be_really_alive(
CONF.timeout_wait_for_service)
with self.local_sql_client(engine) as client:
self._create_admin_user(client, admin_password)
self.stop_db()
self._reset_configuration(config_contents, admin_password)
self.start_mysql()
self._wait_for_mysql_to_be_really_alive(
CONF.timeout_wait_for_service)
LOG.debug("MySQL secure complete.")
class PXCRootAccess(mysql_service.BaseMySqlRootAccess):

View File

@ -17,7 +17,6 @@
import abc
from oslo_log import log as logging
import sqlalchemy
from sqlalchemy.sql.expression import text
from trove.common.i18n import _
@ -36,21 +35,6 @@ class GaleraApp(service.BaseMySqlApp):
super(GaleraApp, self).__init__(status, local_sql_client,
keep_alive_connection_cls)
def _test_mysql(self):
uri = "mysql+pymysql://root:@localhost:3306"
engine = sqlalchemy.create_engine(uri, echo=True)
try:
with self.local_sql_client(engine) as client:
out = client.execute(text("select 1;"))
for line in out:
LOG.debug("line: %s" % line)
return True
except Exception:
return False
def _wait_for_mysql_to_be_really_alive(self, max_time):
utils.poll_until(self._test_mysql, sleep_time=3, time_out=max_time)
def _grant_cluster_replication_privilege(self, replication_user):
LOG.info(_("Granting Replication Slave privilege."))
with self.local_sql_client(self.get_engine()) as client:

View File

@ -230,7 +230,6 @@ class MySqlManager(manager.Manager):
if backup_info:
self._perform_restore(backup_info, context,
mount_point + "/data", app)
LOG.debug("Securing MySQL now.")
app.secure(config_contents)
enable_root_on_restore = (backup_info and
self.mysql_admin().is_root_enabled())

View File

@ -21,6 +21,7 @@ from collections import defaultdict
import os
import re
import six
import urllib
import uuid
from oslo_log import log as logging
@ -47,6 +48,7 @@ from trove.guestagent.db import models
from trove.guestagent import pkg
ADMIN_USER_NAME = "os_admin"
CONNECTION_STR_FORMAT = "mysql+pymysql://%s:%s@127.0.0.1:3306"
LOG = logging.getLogger(__name__)
FLUSH = text(sql_query.FLUSH)
ENGINE = None
@ -606,14 +608,11 @@ class BaseMySqlApp(object):
return ENGINE
pwd = self.get_auth_password()
uri = ("mysql+pymysql://%s:%s@localhost:3306"
% (ADMIN_USER_NAME, pwd.strip()))
ENGINE = sqlalchemy.create_engine(uri,
pool_recycle=7200,
echo=CONF.sql_query_logging,
listeners=[
self.keep_alive_connection_cls()]
)
ENGINE = sqlalchemy.create_engine(
CONNECTION_STR_FORMAT % (ADMIN_USER_NAME,
urllib.quote(pwd.strip())),
pool_recycle=120, echo=CONF.sql_query_logging,
listeners=[self.keep_alive_connection_cls()])
return ENGINE
@classmethod
@ -648,11 +647,13 @@ class BaseMySqlApp(object):
Create a os_admin user with a random password
with all privileges similar to the root user.
"""
LOG.debug("Creating Trove admin user '%s'." % ADMIN_USER_NAME)
localhost = "localhost"
g = sql_query.Grant(permissions='ALL', user=ADMIN_USER_NAME,
host=localhost, grant_option=True, clear=password)
t = text(str(g))
client.execute(t)
LOG.debug("Trove admin user '%s' created." % ADMIN_USER_NAME)
@staticmethod
def _generate_root_password(client):
@ -680,20 +681,26 @@ class BaseMySqlApp(object):
self.start_mysql()
def secure(self, config_contents):
LOG.info(_("Generating admin password."))
admin_password = utils.generate_random_password()
LOG.debug("Securing MySQL now.")
clear_expired_password()
uri = "mysql+pymysql://root:@localhost:3306"
engine = sqlalchemy.create_engine(uri, echo=True)
with self.local_sql_client(engine) as client:
self._remove_anonymous_user(client)
LOG.debug("Generating admin password.")
admin_password = utils.generate_random_password()
engine = sqlalchemy.create_engine(
CONNECTION_STR_FORMAT % ('root', ''), echo=True)
with self.local_sql_client(engine, use_flush=False) as client:
self._create_admin_user(client, admin_password)
self.stop_db()
LOG.debug("Switching to the '%s' user now." % ADMIN_USER_NAME)
engine = sqlalchemy.create_engine(
CONNECTION_STR_FORMAT % (ADMIN_USER_NAME,
urllib.quote(admin_password)),
echo=True)
with self.local_sql_client(engine) as client:
self._remove_anonymous_user(client)
self.stop_db()
self._reset_configuration(config_contents, admin_password)
self.start_mysql()
LOG.debug("MySQL secure complete.")
def _reset_configuration(self, configuration, admin_password=None):
@ -774,12 +781,16 @@ class BaseMySqlApp(object):
raise RuntimeError("Could not stop MySQL!")
def _remove_anonymous_user(self, client):
LOG.debug("Removing anonymous user.")
t = text(sql_query.REMOVE_ANON)
client.execute(t)
LOG.debug("Anonymous user removed.")
def _remove_remote_root_access(self, client):
LOG.debug("Removing root access.")
t = text(sql_query.REMOVE_ROOT)
client.execute(t)
LOG.debug("Root access removed.")
def restart(self):
try:

View File

@ -228,7 +228,8 @@ register(["mongodb_supported"], common_groups,
backup_groups, cluster_actions_groups, configuration_groups,
database_actions_groups, root_actions_groups, user_actions_groups)
register(["pxc_supported"], common_groups,
cluster_actions_groups, root_actions_groups)
backup_groups, configuration_groups, database_actions_groups,
cluster_actions_groups, root_actions_groups, user_actions_groups)
register(["redis_supported"], common_groups,
backup_groups, replication_groups, cluster_actions_groups)
register(["vertica_supported"], common_groups,

View File

@ -474,3 +474,9 @@ class PerconaUserActionsRunner(MysqlUserActionsRunner):
def __init__(self):
super(PerconaUserActionsRunner, self).__init__()
class PxcUserActionsRunner(MysqlUserActionsRunner):
def __init__(self):
super(PxcUserActionsRunner, self).__init__()

View File

@ -426,7 +426,7 @@ class MySqlAdminTest(trove_testtools.TestCase):
dbaas.orig_configuration_manager = dbaas.MySqlApp.configuration_manager
dbaas.MySqlApp.configuration_manager = Mock()
dbaas.orig_get_auth_password = dbaas.MySqlApp.get_auth_password
dbaas.MySqlApp.get_auth_password = Mock()
dbaas.MySqlApp.get_auth_password = Mock(return_value='root_pwd')
self.orig_configuration_manager = \
mysql_common_service.BaseMySqlApp.configuration_manager
mysql_common_service.BaseMySqlApp.configuration_manager = Mock()
@ -1586,8 +1586,6 @@ class MySqlAppMockTest(trove_testtools.TestCase):
app = MySqlApp(mock_status)
app._reset_configuration = MagicMock()
app.start_mysql = MagicMock(return_value=None)
app._wait_for_mysql_to_be_really_alive = MagicMock(
return_value=True)
app.stop_db = MagicMock(return_value=None)
app.secure('foo')
reset_config_calls = [call('foo', auth_pwd_mock.return_value)]