Implement MariaDB Clustering

For MariaDB 10.1, the Galera clustering is included as part of
the base installation, so clustering for MariaDB is implemented
within the MariaDB datastore directly (rather than as a separate
datastore as was done with pxc).

Change Manifest:
- refactor galera clustering support from pxc to galera_common
- incorporate the galera_common component into MariaDB datastore
- a little cleanup of how pxc and mariadb were implementing the
  mysql_common component
- refactored pxc unittests to pxc and galera specific tests
- implemented unittests for MariaDB
- implemented clustering unittests for MariaDB
- mariadb_supported scenario tests run (except replication
  due to slave_of issues)
- cluster scenario tests run successfully

Change-Id: Ieae77cf9ff797472ef13f2532fff1e64bc654151
Implements-blueprint: mariadb-clustering
This commit is contained in:
Morgan Jones 2016-01-29 12:20:43 -05:00 committed by Doug Shelley
parent a2e505941e
commit 22c9c77be7
29 changed files with 966 additions and 564 deletions

View File

@ -680,16 +680,16 @@ pxc_opts = [
help='Minimum number of members in PXC cluster.'),
cfg.StrOpt('api_strategy',
default='trove.common.strategies.cluster.experimental.'
'pxc.api.PXCAPIStrategy',
'galera_common.api.GaleraCommonAPIStrategy',
help='Class that implements datastore-specific API logic.'),
cfg.StrOpt('taskmanager_strategy',
default='trove.common.strategies.cluster.experimental.pxc.'
'taskmanager.PXCTaskManagerStrategy',
default='trove.common.strategies.cluster.experimental.'
'galera_common.taskmanager.GaleraCommonTaskManagerStrategy',
help='Class that implements datastore-specific task manager '
'logic.'),
cfg.StrOpt('guestagent_strategy',
default='trove.common.strategies.cluster.experimental.'
'pxc.guestagent.PXCGuestAgentStrategy',
'galera_common.guestagent.GaleraCommonGuestAgentStrategy',
help='Class that implements datastore-specific Guest Agent API '
'logic.'),
cfg.StrOpt('root_controller',
@ -702,6 +702,7 @@ pxc_opts = [
'in order to be logged in the slow_query log.'),
]
# Redis
redis_group = cfg.OptGroup(
'redis', title='Redis options',
@ -1182,7 +1183,7 @@ mariadb_group = cfg.OptGroup(
'mariadb', title='MariaDB options',
help="Oslo option group designed for MariaDB datastore")
mariadb_opts = [
cfg.ListOpt('tcp_ports', default=["3306"],
cfg.ListOpt('tcp_ports', default=["3306", "4444", "4567", "4568"],
help='List of TCP ports and/or port ranges to open '
'in the security group (only applicable '
'if trove_security_groups_support is True).'),
@ -1250,6 +1251,24 @@ mariadb_opts = [
cfg.IntOpt('guest_log_long_query_time', default=1000,
help='The time in milliseconds that a statement must take in '
'in order to be logged in the slow_query log.'),
cfg.BoolOpt('cluster_support', default=True,
help='Enable clusters to be created and managed.'),
cfg.IntOpt('min_cluster_member_count', default=3,
help='Minimum number of members in MariaDB cluster.'),
cfg.StrOpt('api_strategy',
default='trove.common.strategies.cluster.experimental.'
'galera_common.api.GaleraCommonAPIStrategy',
help='Class that implements datastore-specific API logic.'),
cfg.StrOpt('taskmanager_strategy',
default='trove.common.strategies.cluster.experimental.'
'galera_common.taskmanager.GaleraCommonTaskManagerStrategy',
help='Class that implements datastore-specific task manager '
'logic.'),
cfg.StrOpt('guestagent_strategy',
default='trove.common.strategies.cluster.experimental.'
'galera_common.guestagent.GaleraCommonGuestAgentStrategy',
help='Class that implements datastore-specific Guest Agent API '
'logic.'),
]
# RPC version groups

View File

@ -1,4 +1,5 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
# Copyright 2016 Tesora Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@ -15,13 +16,13 @@ from novaclient import exceptions as nova_exceptions
from oslo_log import log as logging
from trove.cluster import models
from trove.cluster import models as cluster_models
from trove.cluster.tasks import ClusterTasks
from trove.cluster.views import ClusterView
from trove.common import cfg
from trove.common import exception
from trove.common import remote
from trove.common.strategies.cluster import base
from trove.common.strategies.cluster import base as cluster_base
from trove.extensions.mgmt.clusters.views import MgmtClusterView
from trove.instance.models import DBInstance
from trove.instance.models import Instance
@ -33,34 +34,34 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class PXCAPIStrategy(base.BaseAPIStrategy):
class GaleraCommonAPIStrategy(cluster_base.BaseAPIStrategy):
@property
def cluster_class(self):
return PXCCluster
return GaleraCommonCluster
@property
def cluster_view_class(self):
return PXCClusterView
return GaleraCommonClusterView
@property
def mgmt_cluster_view_class(self):
return PXCMgmtClusterView
return GaleraCommonMgmtClusterView
class PXCCluster(models.Cluster):
class GaleraCommonCluster(cluster_models.Cluster):
@staticmethod
def _validate_cluster_instances(context, instances, datastore,
datastore_version):
"""Validate the flavor and volume"""
pxc_conf = CONF.get(datastore_version.manager)
ds_conf = CONF.get(datastore_version.manager)
num_instances = len(instances)
# Check number of instances is at least min_cluster_member_count
if num_instances < pxc_conf.min_cluster_member_count:
if num_instances < ds_conf.min_cluster_member_count:
raise exception.ClusterNumInstancesNotLargeEnough(
num_instances=pxc_conf.min_cluster_member_count)
num_instances=ds_conf.min_cluster_member_count)
# Checking flavors and get delta for quota check
flavor_ids = [instance['flavor_id'] for instance in instances]
@ -78,18 +79,18 @@ class PXCCluster(models.Cluster):
volume_sizes = [instance['volume_size'] for instance in instances
if instance.get('volume_size', None)]
volume_size = None
if pxc_conf.volume_support:
if ds_conf.volume_support:
if len(volume_sizes) != num_instances:
raise exception.ClusterVolumeSizeRequired()
if len(set(volume_sizes)) != 1:
raise exception.ClusterVolumeSizesNotEqual()
volume_size = volume_sizes[0]
models.validate_volume_size(volume_size)
cluster_models.validate_volume_size(volume_size)
deltas['volumes'] = volume_size * num_instances
else:
if len(volume_sizes) > 0:
raise exception.VolumeNotSupported()
ephemeral_support = pxc_conf.device_path
ephemeral_support = ds_conf.device_path
if ephemeral_support and flavor.ephemeral == 0:
raise exception.LocalStorageNotSpecified(flavor=flavor_id)
@ -140,11 +141,11 @@ class PXCCluster(models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
instances, extended_properties):
LOG.debug("Initiating PXC cluster creation.")
LOG.debug("Initiating Galera cluster creation.")
cls._validate_cluster_instances(context, instances, datastore,
datastore_version)
# Updating Cluster Task
db_info = models.DBCluster.create(
db_info = cluster_models.DBCluster.create(
name=name, tenant_id=context.tenant,
datastore_version_id=datastore_version.id,
task_status=ClusterTasks.BUILDING_INITIAL)
@ -156,7 +157,7 @@ class PXCCluster(models.Cluster):
task_api.load(context, datastore_version.manager).create_cluster(
db_info.id)
return PXCCluster(context, db_info, datastore, datastore_version)
return cls(context, db_info, datastore, datastore_version)
def _get_cluster_network_interfaces(self):
nova_client = remote.create_nova_client(self.context)
@ -176,21 +177,23 @@ class PXCCluster(models.Cluster):
datastore = self.ds
datastore_version = self.ds_version
# Get the network of the existing cluster instances.
interface_ids = self._get_cluster_network_interfaces()
for instance in instances:
instance["nics"] = interface_ids
db_info.update(task_status=ClusterTasks.GROWING_CLUSTER)
try:
# Get the network of the existing cluster instances.
interface_ids = self._get_cluster_network_interfaces()
for instance in instances:
instance["nics"] = interface_ids
new_instances = self._create_instances(context, db_info,
datastore, datastore_version,
instances)
new_instances = self._create_instances(
context, db_info, datastore, datastore_version, instances)
task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances])
task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances])
except Exception:
db_info.update(task_status=ClusterTasks.NONE)
return PXCCluster(context, db_info, datastore, datastore_version)
return self.__class__(context, db_info,
datastore, datastore_version)
def shrink(self, instances):
"""Removes instances from a cluster."""
@ -204,19 +207,25 @@ class PXCCluster(models.Cluster):
raise exception.ClusterShrinkMustNotLeaveClusterEmpty()
self.db_info.update(task_status=ClusterTasks.SHRINKING_CLUSTER)
task_api.load(self.context, self.ds_version.manager).shrink_cluster(
self.db_info.id, [instance.id for instance in removal_instances])
try:
task_api.load(self.context, self.ds_version.manager
).shrink_cluster(self.db_info.id,
[instance.id
for instance in removal_instances])
except Exception:
self.db_info.update(task_status=ClusterTasks.NONE)
return PXCCluster(self.context, self.db_info, self.ds, self.ds_version)
return self.__class__(self.context, self.db_info,
self.ds, self.ds_version)
class PXCClusterView(ClusterView):
class GaleraCommonClusterView(ClusterView):
def build_instances(self):
return self._build_instances(['member'], ['member'])
class PXCMgmtClusterView(MgmtClusterView):
class GaleraCommonMgmtClusterView(MgmtClusterView):
def build_instances(self):
return self._build_instances(['member'], ['member'])

View File

@ -1,4 +1,5 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
# Copyright 2016 Tesora Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@ -14,7 +15,7 @@
from oslo_log import log as logging
from trove.common import cfg
from trove.common.strategies.cluster import base
from trove.common.strategies.cluster import base as cluster_base
from trove.guestagent import api as guest_api
@ -22,19 +23,19 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class PXCGuestAgentStrategy(base.BaseGuestAgentStrategy):
class GaleraCommonGuestAgentStrategy(cluster_base.BaseGuestAgentStrategy):
@property
def guest_client_class(self):
return PXCGuestAgentAPI
return GaleraCommonGuestAgentAPI
class PXCGuestAgentAPI(guest_api.API):
class GaleraCommonGuestAgentAPI(guest_api.API):
def install_cluster(self, replication_user, cluster_configuration,
bootstrap):
"""Install the cluster."""
LOG.debug("Installing PXC cluster.")
LOG.debug("Installing Galera cluster.")
self._call("install_cluster", CONF.cluster_usage_timeout,
self.version_cap,
replication_user=replication_user,

View File

@ -1,4 +1,5 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
# Copyright 2016 Tesora Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@ -19,7 +20,7 @@ from trove.common.exception import PollTimeOut
from trove.common.exception import TroveError
from trove.common.i18n import _
from trove.common.remote import create_nova_client
from trove.common.strategies.cluster import base
from trove.common.strategies.cluster import base as cluster_base
from trove.common.template import ClusterConfigTemplate
from trove.common import utils
from trove.extensions.common import models as ext_models
@ -32,10 +33,9 @@ import trove.taskmanager.models as task_models
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
USAGE_SLEEP_TIME = CONF.usage_sleep_time # seconds.
class PXCTaskManagerStrategy(base.BaseTaskManagerStrategy):
class GaleraCommonTaskManagerStrategy(cluster_base.BaseTaskManagerStrategy):
@property
def task_manager_api_class(self):
@ -43,10 +43,10 @@ class PXCTaskManagerStrategy(base.BaseTaskManagerStrategy):
@property
def task_manager_cluster_tasks_class(self):
return PXCClusterTasks
return GaleraCommonClusterTasks
class PXCClusterTasks(task_models.ClusterTasks):
class GaleraCommonClusterTasks(task_models.ClusterTasks):
CLUSTER_REPLICATION_USER = "clusterrepuser"
@ -89,16 +89,16 @@ class PXCClusterTasks(task_models.ClusterTasks):
for instance in instances]
# Create replication user and password for synchronizing the
# PXC cluster
# galera cluster
replication_user = {
"name": self.CLUSTER_REPLICATION_USER,
"password": utils.generate_random_password(),
}
# PXC cluster name must be unique and be shorter than a full
# Galera cluster name must be unique and be shorter than a full
# uuid string so we remove the hyphens and chop it off. It was
# recommended to be 16 chars or less.
# (this is not currently documented on PXC docs)
# (this is not currently documented on Galera docs)
cluster_name = utils.generate_uuid().replace("-", "")[:16]
LOG.debug("Configuring cluster configuration.")
@ -163,7 +163,7 @@ class PXCClusterTasks(task_models.ClusterTasks):
return
def grow_cluster(self, context, cluster_id, new_instance_ids):
LOG.debug("Begin pxc grow_cluster for id: %s." % cluster_id)
LOG.debug("Begin Galera grow_cluster for id: %s." % cluster_id)
def _grow_cluster():
@ -256,7 +256,7 @@ class PXCClusterTasks(task_models.ClusterTasks):
LOG.debug("End grow_cluster for id: %s." % cluster_id)
def shrink_cluster(self, context, cluster_id, removal_instance_ids):
LOG.debug("Begin pxc shrink_cluster for id: %s." % cluster_id)
LOG.debug("Begin Galera shrink_cluster for id: %s." % cluster_id)
def _shrink_cluster():
removal_instances = [Instance.load(context, instance_id)

View File

@ -14,23 +14,16 @@
# under the License.
#
from oslo_utils import importutils
from trove.guestagent.datastore.mysql_common import manager
from trove.guestagent.datastore.experimental.mariadb import (
service as mariadb_service)
from trove.guestagent.datastore.galera_common import manager as galera_manager
from trove.guestagent.datastore.mysql_common import service as mysql_service
MYSQL_APP = ("trove.guestagent.datastore.experimental.mariadb.service."
"MySqlApp")
MYSQL_APP_STATUS = ("trove.guestagent.datastore.experimental.mariadb.service."
"MySqlAppStatus")
MYSQL_ADMIN = ("trove.guestagent.datastore.experimental.mariadb.service."
"MySqlAdmin")
class Manager(manager.MySqlManager):
class Manager(galera_manager.GaleraManager):
def __init__(self):
mysql_app = importutils.import_class(MYSQL_APP)
mysql_app_status = importutils.import_class(MYSQL_APP_STATUS)
mysql_admin = importutils.import_class(MYSQL_ADMIN)
super(Manager, self).__init__(mysql_app, mysql_app_status, mysql_admin)
super(Manager, self).__init__(
mariadb_service.MariaDBApp,
mysql_service.BaseMySqlAppStatus,
mariadb_service.MariaDBAdmin)

View File

@ -15,27 +15,37 @@
#
from oslo_log import log as logging
from trove.guestagent.datastore.mysql_common import service
from trove.guestagent.datastore.galera_common import service as galera_service
from trove.guestagent.datastore.mysql_common import service as mysql_service
LOG = logging.getLogger(__name__)
class KeepAliveConnection(service.BaseKeepAliveConnection):
pass
class MariaDBApp(galera_service.GaleraApp):
class MySqlAppStatus(service.BaseMySqlAppStatus):
pass
class LocalSqlClient(service.BaseLocalSqlClient):
pass
class MySqlApp(service.BaseMySqlApp):
def __init__(self, status):
super(MySqlApp, self).__init__(status, LocalSqlClient,
KeepAliveConnection)
super(MariaDBApp, self).__init__(
status, mysql_service.BaseLocalSqlClient,
mysql_service.BaseKeepAliveConnection)
@property
def mysql_service(self):
result = super(MariaDBApp, self).mysql_service
if result['type'] == 'sysvinit':
result['cmd_bootstrap_galera_cluster'] = (
"sudo service %s bootstrap"
% result['service'])
elif result['type'] == 'systemd':
# TODO(mwj 2016/01/28): determine RHEL start for MariaDB Cluster
result['cmd_bootstrap_galera_cluster'] = (
"sudo systemctl start %s@bootstrap.service"
% result['service'])
return result
@property
def cluster_configuration(self):
return self.configuration_manager.get_value('galera')
def _get_slave_status(self):
with self.local_sql_client(self.get_engine()) as client:
@ -70,13 +80,15 @@ class MySqlApp(service.BaseMySqlApp):
client.execute("SELECT MASTER_GTID_WAIT('%s')" % txn)
class MySqlRootAccess(service.BaseMySqlRootAccess):
class MariaDBRootAccess(mysql_service.BaseMySqlRootAccess):
def __init__(self):
super(MySqlRootAccess, self).__init__(LocalSqlClient,
MySqlApp(MySqlAppStatus.get()))
super(MariaDBRootAccess, self).__init__(
mysql_service.BaseLocalSqlClient,
MariaDBApp(mysql_service.BaseMySqlAppStatus.get()))
class MySqlAdmin(service.BaseMySqlAdmin):
class MariaDBAdmin(mysql_service.BaseMySqlAdmin):
def __init__(self):
super(MySqlAdmin, self).__init__(LocalSqlClient, MySqlRootAccess(),
MySqlApp)
super(MariaDBAdmin, self).__init__(
mysql_service.BaseLocalSqlClient, MariaDBRootAccess(),
MariaDBApp)

View File

@ -14,72 +14,14 @@
# under the License.
#
from oslo_log import log as logging
from oslo_utils import importutils
from trove.common.i18n import _
from trove.common import instance as rd_instance
from trove.guestagent.datastore.mysql_common import manager
from trove.guestagent.datastore.experimental.pxc import service as pxc_service
from trove.guestagent.datastore.galera_common import manager
from trove.guestagent.datastore.mysql_common import service as mysql_service
MYSQL_APP = ("trove.guestagent.datastore.experimental.pxc.service."
"PXCApp")
MYSQL_APP_STATUS = ("trove.guestagent.datastore.experimental.pxc.service."
"PXCAppStatus")
MYSQL_ADMIN = ("trove.guestagent.datastore.experimental.pxc.service."
"PXCAdmin")
LOG = logging.getLogger(__name__)
class Manager(manager.MySqlManager):
class Manager(manager.GaleraManager):
def __init__(self):
mysql_app = importutils.import_class(MYSQL_APP)
mysql_app_status = importutils.import_class(MYSQL_APP_STATUS)
mysql_admin = importutils.import_class(MYSQL_ADMIN)
super(Manager, self).__init__(mysql_app, mysql_app_status, mysql_admin)
def do_prepare(self, context, packages, databases, memory_mb, users,
device_path, mount_point, backup_info,
config_contents, root_password, overrides,
cluster_config, snapshot):
self.volume_do_not_start_on_reboot = True
super(Manager, self).do_prepare(
context, packages, databases, memory_mb, users,
device_path, mount_point, backup_info,
config_contents, root_password, overrides,
cluster_config, snapshot)
def install_cluster(self, context, replication_user, cluster_configuration,
bootstrap):
app = self.mysql_app(self.mysql_app_status.get())
try:
app.install_cluster(
replication_user, cluster_configuration, bootstrap)
LOG.debug("install_cluster call has finished.")
except Exception:
LOG.exception(_('Cluster installation failed.'))
app.status.set_status(
rd_instance.ServiceStatuses.FAILED)
raise
def reset_admin_password(self, context, admin_password):
LOG.debug("Storing the admin password on the instance.")
app = self.mysql_app(self.mysql_app_status.get())
app.reset_admin_password(admin_password)
def get_cluster_context(self, context):
LOG.debug("Getting the cluster context.")
app = self.mysql_app(self.mysql_app_status.get())
return app.get_cluster_context()
def write_cluster_configuration_overrides(self, context,
cluster_configuration):
LOG.debug("Apply the updated cluster configuration.")
app = self.mysql_app(self.mysql_app_status.get())
app.write_cluster_configuration_overrides(cluster_configuration)
def enable_root_with_password(self, context, root_password=None):
return self.mysql_admin().enable_root(root_password)
super(Manager, self).__init__(pxc_service.PXCApp,
mysql_service.BaseMySqlAppStatus,
pxc_service.PXCAdmin)

View File

@ -18,66 +18,58 @@ 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
from trove.guestagent.common import sql_query
from trove.guestagent.datastore.experimental.pxc import system
from trove.guestagent.datastore.mysql_common import service
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
LOG = logging.getLogger(__name__)
CONF = service.CONF
CNF_CLUSTER = "cluster"
CONF = cfg.CONF
class KeepAliveConnection(service.BaseKeepAliveConnection):
pass
class PXCApp(galera_service.GaleraApp):
class PXCAppStatus(service.BaseMySqlAppStatus):
pass
class LocalSqlClient(service.BaseLocalSqlClient):
pass
class PXCApp(service.BaseMySqlApp):
def __init__(self, status):
super(PXCApp, self).__init__(status, LocalSqlClient,
KeepAliveConnection)
super(PXCApp, self).__init__(
status, mysql_service.BaseLocalSqlClient,
mysql_service.BaseKeepAliveConnection)
def _test_mysql(self):
engine = sqlalchemy.create_engine("mysql://root:@localhost:3306",
echo=True)
try:
with LocalSqlClient(engine) as client:
out = client.execute(text("select 1;"))
for line in out:
LOG.debug("line: %s" % line)
return True
except Exception:
return False
@property
def mysql_service(self):
result = super(PXCApp, self).mysql_service
if result['type'] == 'sysvinit':
result['cmd_bootstrap_galera_cluster'] = (
"sudo service %s bootstrap-pxc" % result['service'])
elif result['type'] == 'systemd':
result['cmd_bootstrap_galera_cluster'] = (
"sudo systemctl start %s@bootstrap.service"
% result['service'])
return result
def _wait_for_mysql_to_be_really_alive(self, max_time):
utils.poll_until(self._test_mysql, sleep_time=3, time_out=max_time)
@property
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()
service.clear_expired_password()
mysql_service.clear_expired_password()
engine = sqlalchemy.create_engine("mysql://root:@localhost:3306",
echo=True)
with LocalSqlClient(engine) as client:
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 LocalSqlClient(engine) as client:
with self.local_sql_client(engine) as client:
query = text("select Host, User from mysql.user;")
client.execute(query)
except Exception:
@ -87,7 +79,7 @@ class PXCApp(service.BaseMySqlApp):
# removing the annon users.
self._wait_for_mysql_to_be_really_alive(
CONF.timeout_wait_for_service)
with LocalSqlClient(engine) as client:
with self.local_sql_client(engine) as client:
self._create_admin_user(client, admin_password)
self.stop_db()
@ -97,68 +89,16 @@ class PXCApp(service.BaseMySqlApp):
CONF.timeout_wait_for_service)
LOG.debug("MySQL secure complete.")
def _grant_cluster_replication_privilege(self, replication_user):
LOG.info(_("Granting Replication Slave privilege."))
with self.local_sql_client(self.get_engine()) as client:
perms = ['REPLICATION CLIENT', 'RELOAD', 'LOCK TABLES']
g = sql_query.Grant(permissions=perms,
user=replication_user['name'],
clear=replication_user['password'])
t = text(str(g))
client.execute(t)
def _bootstrap_cluster(self, timeout=120):
LOG.info(_("Bootstraping cluster."))
try:
mysql_service = system.service_discovery(
service.MYSQL_SERVICE_CANDIDATES)
utils.execute_with_timeout(
mysql_service['cmd_bootstrap_pxc_cluster'],
shell=True, timeout=timeout)
except KeyError:
LOG.exception(_("Error bootstrapping cluster."))
raise RuntimeError(_("Service is not discovered."))
class PXCRootAccess(mysql_service.BaseMySqlRootAccess):
def write_cluster_configuration_overrides(self, cluster_configuration):
self.configuration_manager.apply_system_override(
cluster_configuration, CNF_CLUSTER)
def install_cluster(self, replication_user, cluster_configuration,
bootstrap=False):
LOG.info(_("Installing cluster configuration."))
self._grant_cluster_replication_privilege(replication_user)
self.stop_db()
self.write_cluster_configuration_overrides(cluster_configuration)
self.wipe_ib_logfiles()
LOG.debug("bootstrap the instance? : %s" % bootstrap)
# Have to wait to sync up the joiner instances with the donor instance.
if bootstrap:
self._bootstrap_cluster(timeout=CONF.restore_usage_timeout)
else:
self.start_mysql(timeout=CONF.restore_usage_timeout)
def get_cluster_context(self):
auth = self.configuration_manager.get_value('mysqld').get(
"wsrep_sst_auth").replace('"', '')
cluster_name = self.configuration_manager.get_value(
'mysqld').get("wsrep_cluster_name")
return {
'replication_user': {
'name': auth.split(":")[0],
'password': auth.split(":")[1],
},
'cluster_name': cluster_name,
'admin_password': self.get_auth_password()
}
class PXCRootAccess(service.BaseMySqlRootAccess):
def __init__(self):
super(PXCRootAccess, self).__init__(LocalSqlClient,
PXCApp(PXCAppStatus.get()))
super(PXCRootAccess, self).__init__(
mysql_service.BaseLocalSqlClient,
PXCApp(mysql_service.BaseMySqlAppStatus.get()))
class PXCAdmin(service.BaseMySqlAdmin):
class PXCAdmin(mysql_service.BaseMySqlAdmin):
def __init__(self):
super(PXCAdmin, self).__init__(LocalSqlClient, PXCRootAccess(),
PXCApp)
super(PXCAdmin, self).__init__(
mysql_service.BaseLocalSqlClient, PXCRootAccess(), PXCApp)

View File

@ -1,27 +0,0 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from trove.guestagent.common import operating_system
def service_discovery(service_candidates):
result = operating_system.service_discovery(service_candidates)
if result['type'] == 'sysvinit':
result['cmd_bootstrap_pxc_cluster'] = ("sudo service %s bootstrap-pxc"
% result['service'])
elif result['type'] == 'systemd':
result['cmd_bootstrap_pxc_cluster'] = ("sudo systemctl start "
"%s@bootstrap.service"
% result['service'])
return result

View File

@ -0,0 +1,81 @@
# Copyright 2016 Tesora, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from oslo_log import log as logging
from trove.common.i18n import _
from trove.common import instance as rd_instance
from trove.guestagent.datastore.mysql_common import manager
LOG = logging.getLogger(__name__)
class GaleraManager(manager.MySqlManager):
def __init__(self, mysql_app, mysql_app_status, mysql_admin,
manager_name='galera'):
super(GaleraManager, self).__init__(
mysql_app, mysql_app_status, mysql_admin, manager_name)
self._mysql_app = mysql_app
self._mysql_app_status = mysql_app_status
self._mysql_admin = mysql_admin
self.volume_do_not_start_on_reboot = False
def do_prepare(self, context, packages, databases, memory_mb, users,
device_path, mount_point, backup_info,
config_contents, root_password, overrides,
cluster_config, snapshot):
self.volume_do_not_start_on_reboot = True
super(GaleraManager, self).do_prepare(
context, packages, databases, memory_mb, users,
device_path, mount_point, backup_info,
config_contents, root_password, overrides,
cluster_config, snapshot)
def install_cluster(self, context, replication_user, cluster_configuration,
bootstrap):
app = self.mysql_app(self.mysql_app_status.get())
try:
app.install_cluster(
replication_user, cluster_configuration, bootstrap)
LOG.debug("install_cluster call has finished.")
except Exception:
LOG.exception(_('Cluster installation failed.'))
app.status.set_status(
rd_instance.ServiceStatuses.FAILED)
raise
def reset_admin_password(self, context, admin_password):
LOG.debug("Storing the admin password on the instance.")
app = self.mysql_app(self.mysql_app_status.get())
app.reset_admin_password(admin_password)
def get_cluster_context(self, context):
LOG.debug("Getting the cluster context.")
app = self.mysql_app(self.mysql_app_status.get())
return app.get_cluster_context()
def write_cluster_configuration_overrides(self, context,
cluster_configuration):
LOG.debug("Apply the updated cluster configuration.")
app = self.mysql_app(self.mysql_app_status.get())
app.write_cluster_configuration_overrides(cluster_configuration)
def enable_root_with_password(self, context, root_password=None):
return self.mysql_admin().enable_root(root_password)

View File

@ -0,0 +1,109 @@
# Copyright 2016 Tesora, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import abc
from oslo_log import log as logging
import sqlalchemy
from sqlalchemy.sql.expression import text
from trove.common.i18n import _
from trove.common import utils
from trove.guestagent.common import sql_query
from trove.guestagent.datastore.mysql_common import service
LOG = logging.getLogger(__name__)
CONF = service.CONF
class GaleraApp(service.BaseMySqlApp):
def __init__(self, status, local_sql_client, keep_alive_connection_cls):
super(GaleraApp, self).__init__(status, local_sql_client,
keep_alive_connection_cls)
def _test_mysql(self):
engine = sqlalchemy.create_engine("mysql://root:@localhost:3306",
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:
perms = ['REPLICATION CLIENT', 'RELOAD', 'LOCK TABLES']
g = sql_query.Grant(permissions=perms,
user=replication_user['name'],
clear=replication_user['password'])
t = text(str(g))
client.execute(t)
def _bootstrap_cluster(self, timeout=120):
LOG.info(_("Bootstraping cluster."))
try:
utils.execute_with_timeout(
self.mysql_service['cmd_bootstrap_galera_cluster'],
shell=True, timeout=timeout)
except KeyError:
LOG.exception(_("Error bootstrapping cluster."))
raise RuntimeError(_("Service is not discovered."))
def write_cluster_configuration_overrides(self, cluster_configuration):
self.configuration_manager.apply_system_override(
cluster_configuration, 'cluster')
def install_cluster(self, replication_user, cluster_configuration,
bootstrap=False):
LOG.info(_("Installing cluster configuration."))
self._grant_cluster_replication_privilege(replication_user)
self.stop_db()
self.write_cluster_configuration_overrides(cluster_configuration)
self.wipe_ib_logfiles()
LOG.debug("bootstrap the instance? : %s" % bootstrap)
# Have to wait to sync up the joiner instances with the donor instance.
if bootstrap:
self._bootstrap_cluster(timeout=CONF.restore_usage_timeout)
else:
self.start_mysql(timeout=CONF.restore_usage_timeout)
@abc.abstractproperty
def cluster_configuration(self):
"""
Returns the cluster section from the configuration manager.
"""
def get_cluster_context(self):
auth = self.cluster_configuration.get(
"wsrep_sst_auth").replace('"', '')
cluster_name = self.cluster_configuration.get("wsrep_cluster_name")
return {
'replication_user': {
'name': auth.split(":")[0],
'password': auth.split(":")[1],
},
'cluster_name': cluster_name,
'admin_password': self.get_auth_password()
}

View File

@ -68,7 +68,6 @@ OS_NAME = operating_system.get_os()
MYSQL_CONFIG = {operating_system.REDHAT: "/etc/my.cnf",
operating_system.DEBIAN: "/etc/mysql/my.cnf",
operating_system.SUSE: "/etc/my.cnf"}[OS_NAME]
MYSQL_SERVICE_CANDIDATES = ["mysql", "mysqld", "mysql-server"]
MYSQL_BIN_CANDIDATES = ["/usr/sbin/mysqld", "/usr/libexec/mysqld"]
MYSQL_OWNER = 'mysql'
CNF_EXT = 'cnf'
@ -588,6 +587,11 @@ class BaseMySqlApp(object):
def keep_alive_connection_cls(self):
return self._keep_alive_connection_cls
@property
def mysql_service(self):
MYSQL_SERVICE_CANDIDATES = ["mysql", "mysqld", "mysql-server"]
return operating_system.service_discovery(MYSQL_SERVICE_CANDIDATES)
configuration_manager = ConfigurationManager(
MYSQL_CONFIG, MYSQL_OWNER, MYSQL_OWNER, CFG_CODEC, requires_root=True,
override_strategy=ImportOverrideStrategy(CNF_INCLUDE_DIR, CNF_EXT))
@ -737,18 +741,15 @@ class BaseMySqlApp(object):
def _enable_mysql_on_boot(self):
LOG.debug("Enabling MySQL on boot.")
try:
mysql_service = operating_system.service_discovery(
MYSQL_SERVICE_CANDIDATES)
utils.execute_with_timeout(mysql_service['cmd_enable'], shell=True)
utils.execute_with_timeout(self.mysql_service['cmd_enable'],
shell=True)
except KeyError:
LOG.exception(_("Error enabling MySQL start on boot."))
raise RuntimeError("Service is not discovered.")
def _disable_mysql_on_boot(self):
try:
mysql_service = operating_system.service_discovery(
MYSQL_SERVICE_CANDIDATES)
utils.execute_with_timeout(mysql_service['cmd_disable'],
utils.execute_with_timeout(self.mysql_service['cmd_disable'],
shell=True)
except KeyError:
LOG.exception(_("Error disabling MySQL start on boot."))
@ -759,9 +760,8 @@ class BaseMySqlApp(object):
if do_not_start_on_reboot:
self._disable_mysql_on_boot()
try:
mysql_service = operating_system.service_discovery(
MYSQL_SERVICE_CANDIDATES)
utils.execute_with_timeout(mysql_service['cmd_stop'], shell=True)
utils.execute_with_timeout(self.mysql_service['cmd_stop'],
shell=True)
except KeyError:
LOG.exception(_("Error stopping MySQL."))
raise RuntimeError("Service is not discovered.")
@ -951,10 +951,8 @@ class BaseMySqlApp(object):
self._enable_mysql_on_boot()
try:
mysql_service = operating_system.service_discovery(
MYSQL_SERVICE_CANDIDATES)
utils.execute_with_timeout(mysql_service['cmd_start'], shell=True,
timeout=timeout)
utils.execute_with_timeout(self.mysql_service['cmd_start'],
shell=True, timeout=timeout)
except KeyError:
raise RuntimeError("Service is not discovered.")
except exception.ProcessExecutionError:

View File

@ -0,0 +1,23 @@
[mysqld]
bind-address=0.0.0.0
default-storage-engine=innodb
[galera]
binlog_format=ROW
innodb_autoinc_lock_mode=2
innodb_flush_log_at_trx_commit=0
innodb_doublewrite=1
query_cache_size=0
wsrep_on=ON
wsrep_slave_threads=8
wsrep_provider=/usr/lib/libgalera_smm.so
wsrep_provider_options="gcache.size={{ (128 * flavor['ram']/512)|int }}M; gcache.page_size=1G"
wsrep_sst_method=rsync
wsrep_sst_auth="{{ replication_user_pass }}"
wsrep_cluster_address="gcomm://{{ cluster_ips }}"
wsrep_cluster_name={{ cluster_name }}
wsrep_node_name={{ instance_name }}
wsrep_node_address={{ instance_ip }}

View File

@ -193,10 +193,13 @@ register(["couchdb_supported"], common_groups)
register(["postgresql_supported"], common_groups,
backup_groups, database_actions_groups, configuration_groups,
root_actions_groups, user_actions_groups)
register(["mariadb_supported", "mysql_supported", "percona_supported"],
common_groups,
register(["mysql_supported", "percona_supported"], common_groups,
backup_groups, configuration_groups, database_actions_groups,
replication_groups, root_actions_groups, user_actions_groups)
register(["mariadb_supported"], common_groups,
backup_groups, cluster_actions_groups, configuration_groups,
database_actions_groups, replication_groups, root_actions_groups,
user_actions_groups)
register(["mongodb_supported"], common_groups,
backup_groups, cluster_actions_groups, configuration_groups,
database_actions_groups, root_actions_groups, user_actions_groups)

View File

@ -57,6 +57,7 @@ class CassandraClient(object):
class CassandraHelper(TestHelper):
DATA_COLUMN_NAME = 'value'
cluster_node_count = 2
def __init__(self, expected_override_name):
super(CassandraHelper, self).__init__(expected_override_name)

View File

@ -18,6 +18,8 @@ from trove.tests.scenario.helpers.mysql_helper import MysqlHelper
class MariadbHelper(MysqlHelper):
cluster_node_count = 3
def __init__(self, expected_override_name):
super(MariadbHelper, self).__init__(expected_override_name)

View File

@ -18,6 +18,8 @@ from trove.tests.scenario.helpers.test_helper import TestHelper
class MongodbHelper(TestHelper):
cluster_node_count = 2
def __init__(self, expected_override_name):
super(MongodbHelper, self).__init__(expected_override_name)

View File

@ -18,5 +18,7 @@ from trove.tests.scenario.helpers.mysql_helper import MysqlHelper
class PxcHelper(MysqlHelper):
cluster_node_count = 3
def __init__(self, expected_override_name):
super(PxcHelper, self).__init__(expected_override_name)

View File

@ -22,6 +22,8 @@ from trove.tests.scenario.runners.test_runners import TestRunner
class RedisHelper(TestHelper):
cluster_node_count = 2
def __init__(self, expected_override_name):
super(RedisHelper, self).__init__(expected_override_name)

View File

@ -18,6 +18,7 @@ import os
from proboscis import SkipTest
import time as timer
from trove.common import cfg
from trove.common import exception
from trove.common.utils import poll_until
from trove.tests.scenario.helpers.test_helper import DataType
@ -26,6 +27,9 @@ from trove.tests.util.check import TypeCheck
from troveclient.compat import exceptions
CONF = cfg.CONF
class ClusterActionsRunner(TestRunner):
USE_CLUSTER_ID_FLAG = 'TESTS_USE_CLUSTER_ID'
@ -47,9 +51,12 @@ class ClusterActionsRunner(TestRunner):
def has_do_not_delete_cluster(self):
return self.has_env_flag(self.DO_NOT_DELETE_CLUSTER_FLAG)
def run_cluster_create(self, num_nodes=2, expected_task_name='BUILDING',
def run_cluster_create(self, num_nodes=None, expected_task_name='BUILDING',
expected_instance_states=['BUILD', 'ACTIVE'],
expected_http_code=200):
if not num_nodes:
num_nodes = self.test_helper.cluster_node_count
instances_def = [
self.build_flavor(
flavor_id=self.instance_info.dbaas_flavor_href,
@ -330,23 +337,19 @@ class ClusterActionsRunner(TestRunner):
self.assert_client_code(404)
class MariadbClusterActionsRunner(ClusterActionsRunner):
def run_cluster_root_enable(self):
raise SkipTest("Operation is currently not supported.")
class RedisClusterActionsRunner(ClusterActionsRunner):
def run_cluster_root_enable(self):
raise SkipTest("Operation is currently not supported.")
class MongodbClusterActionsRunner(ClusterActionsRunner):
def run_cluster_create(self, num_nodes=3, expected_task_name='BUILDING',
expected_instance_states=['BUILD', 'ACTIVE'],
expected_http_code=200):
super(MongodbClusterActionsRunner, self).run_cluster_create(
num_nodes=num_nodes, expected_task_name=expected_task_name,
expected_instance_states=expected_instance_states,
expected_http_code=expected_http_code)
class PxcClusterActionsRunner(ClusterActionsRunner):
def run_cluster_create(self, num_nodes=3, expected_task_name='BUILDING',
expected_instance_states=['BUILD', 'ACTIVE'],
expected_http_code=200):
super(PxcClusterActionsRunner, self).run_cluster_create(
num_nodes=num_nodes, expected_task_name=expected_task_name,
expected_instance_states=expected_instance_states,
expected_http_code=expected_http_code)
def run_cluster_root_enable(self):
raise SkipTest("Operation is currently not supported.")

View File

@ -15,15 +15,17 @@ import uuid
from mock import Mock
from mock import patch
from novaclient import exceptions as nova_exceptions
from trove.cluster.models import Cluster
from trove.cluster.models import ClusterTasks
from trove.cluster.models import DBCluster
from trove.common import cfg
from trove.common import exception
from trove.common import remote
from trove.common.strategies.cluster.experimental.pxc import (
api as pxc_api)
from trove.common.strategies.cluster.experimental.galera_common import (
api as galera_api)
from trove.instance import models as inst_models
from trove.quota.quota import QUOTAS
from trove.taskmanager import api as task_api
@ -61,9 +63,8 @@ class ClusterTest(trove_testtools.TestCase):
self.dv = Mock()
self.dv.manager = "pxc"
self.datastore_version = self.dv
self.cluster = pxc_api.PXCCluster(self.context, self.db_info,
self.datastore,
self.datastore_version)
self.cluster = galera_api.GaleraCommonCluster(
self.context, self.db_info, self.datastore, self.datastore_version)
self.instances = [{'volume_size': 1, 'flavor_id': '1234'},
{'volume_size': 1, 'flavor_id': '1234'},
{'volume_size': 1, 'flavor_id': '1234'}]
@ -130,7 +131,7 @@ class ClusterTest(trove_testtools.TestCase):
)
@patch.object(remote, 'create_nova_client')
@patch.object(pxc_api, 'CONF')
@patch.object(galera_api, 'CONF')
def test_create_storage_specified_with_no_volume_support(self,
mock_conf,
mock_client):
@ -150,7 +151,7 @@ class ClusterTest(trove_testtools.TestCase):
)
@patch.object(remote, 'create_nova_client')
@patch.object(pxc_api, 'CONF')
@patch.object(galera_api, 'CONF')
def test_create_storage_not_specified_and_no_ephemeral_flavor(self,
mock_conf,
mock_client):
@ -218,8 +219,30 @@ class ClusterTest(trove_testtools.TestCase):
mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
@patch.object(inst_models.Instance, 'create')
@patch.object(DBCluster, 'create')
@patch.object(task_api, 'load')
@patch.object(QUOTAS, 'check_quotas')
@patch.object(remote, 'create_nova_client')
def test_create_over_limit(self, mock_client, mock_check_quotas,
mock_task_api, mock_db_create, mock_ins_create):
instances = [{'volume_size': 1, 'flavor_id': '1234'},
{'volume_size': 1, 'flavor_id': '1234'},
{'volume_size': 1, 'flavor_id': '1234'},
{'volume_size': 1, 'flavor_id': '1234'}]
flavors = Mock()
mock_client.return_value.flavors = flavors
self.cluster.create(Mock(),
self.cluster_name,
self.datastore,
self.datastore_version,
instances, {})
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(4, mock_ins_create.call_count)
@patch.object(inst_models.DBInstance, 'find_all')
@patch.object(pxc_api, 'CONF')
@patch.object(galera_api, 'CONF')
@patch.object(inst_models.Instance, 'create')
@patch.object(DBCluster, 'create')
@patch.object(task_api, 'load')
@ -284,9 +307,10 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster.delete()
mock_update_db.assert_called_with(task_status=ClusterTasks.DELETING)
@patch.object(pxc_api.PXCCluster, '_get_cluster_network_interfaces')
@patch.object(galera_api.GaleraCommonCluster,
'_get_cluster_network_interfaces')
@patch.object(DBCluster, 'update')
@patch.object(pxc_api, 'CONF')
@patch.object(galera_api, 'CONF')
@patch.object(inst_models.Instance, 'create')
@patch.object(task_api, 'load')
@patch.object(QUOTAS, 'check_quotas')
@ -313,7 +337,7 @@ class ClusterTest(trove_testtools.TestCase):
exception.ClusterShrinkMustNotLeaveClusterEmpty,
self.cluster.shrink, [instance])
@patch.object(pxc_api.PXCCluster, '__init__')
@patch.object(galera_api.GaleraCommonCluster, '__init__')
@patch.object(task_api, 'load')
@patch.object(DBCluster, 'update')
@patch.object(inst_models.DBInstance, 'find_all')

View File

@ -50,6 +50,8 @@ from trove.guestagent.datastore.experimental.couchdb import (
service as couchdb_service)
from trove.guestagent.datastore.experimental.db2 import (
service as db2service)
from trove.guestagent.datastore.experimental.mariadb import (
service as mariadb_service)
from trove.guestagent.datastore.experimental.mongodb import (
service as mongo_service)
from trove.guestagent.datastore.experimental.mongodb import (
@ -62,8 +64,6 @@ from trove.guestagent.datastore.experimental.postgresql.service import (
status as pg_status)
from trove.guestagent.datastore.experimental.pxc import (
service as pxc_service)
from trove.guestagent.datastore.experimental.pxc import (
system as pxc_system)
from trove.guestagent.datastore.experimental.redis import service as rservice
from trove.guestagent.datastore.experimental.redis.service import RedisApp
from trove.guestagent.datastore.experimental.redis import system as RedisSystem
@ -78,7 +78,7 @@ from trove.guestagent.datastore.mysql.service import MySqlAdmin
from trove.guestagent.datastore.mysql.service import MySqlApp
from trove.guestagent.datastore.mysql.service import MySqlAppStatus
from trove.guestagent.datastore.mysql.service import MySqlRootAccess
import trove.guestagent.datastore.mysql_common.service as dbaas_base
import trove.guestagent.datastore.mysql_common.service as mysql_common_service
import trove.guestagent.datastore.service as base_datastore_service
from trove.guestagent.datastore.service import BaseDbStatus
from trove.guestagent.db import models
@ -149,32 +149,32 @@ class DbaasTest(trove_testtools.TestCase):
def setUp(self):
super(DbaasTest, self).setUp()
self.orig_utils_execute_with_timeout = \
dbaas_base.utils.execute_with_timeout
self.orig_utils_execute = dbaas_base.utils.execute
mysql_common_service.utils.execute_with_timeout
self.orig_utils_execute = mysql_common_service.utils.execute
def tearDown(self):
super(DbaasTest, self).tearDown()
dbaas_base.utils.execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
dbaas_base.utils.execute = self.orig_utils_execute
mysql_common_service.utils.execute = self.orig_utils_execute
@patch.object(operating_system, 'remove')
def test_clear_expired_password(self, mock_remove):
secret_content = ("# The random password set for the "
"root user at Wed May 14 14:06:38 2014 "
"(local time): somepassword")
with patch.object(dbaas_base.utils, 'execute',
with patch.object(mysql_common_service.utils, 'execute',
return_value=(secret_content, None)):
dbaas_base.clear_expired_password()
self.assertEqual(2, dbaas_base.utils.execute.call_count)
mysql_common_service.clear_expired_password()
self.assertEqual(2, mysql_common_service.utils.execute.call_count)
self.assertEqual(1, mock_remove.call_count)
@patch.object(operating_system, 'remove')
def test_no_secret_content_clear_expired_password(self, mock_remove):
with patch.object(dbaas_base.utils, 'execute',
with patch.object(mysql_common_service.utils, 'execute',
return_value=('', None)):
dbaas_base.clear_expired_password()
self.assertEqual(1, dbaas_base.utils.execute.call_count)
mysql_common_service.clear_expired_password()
self.assertEqual(1, mysql_common_service.utils.execute.call_count)
mock_remove.assert_not_called()
@patch.object(operating_system, 'remove')
@ -185,44 +185,50 @@ class DbaasTest(trove_testtools.TestCase):
secret_content = ("# The random password set for the "
"root user at Wed May 14 14:06:38 2014 "
"(local time): somepassword")
with patch.object(dbaas_base.utils, 'execute',
with patch.object(mysql_common_service.utils, 'execute',
side_effect=[(secret_content, None),
ProcessExecutionError]):
dbaas_base.clear_expired_password()
self.assertEqual(2, dbaas_base.utils.execute.call_count)
mysql_common_service.clear_expired_password()
self.assertEqual(2, mysql_common_service.utils.execute.call_count)
mock_remove.assert_not_called()
@patch('trove.guestagent.datastore.mysql_common.service.LOG')
@patch.object(operating_system, 'remove')
@patch.object(dbaas_base.utils, 'execute',
@patch.object(mysql_common_service.utils, 'execute',
side_effect=ProcessExecutionError)
def test_fail_retrieve_secret_content_clear_expired_password(self,
mock_execute,
mock_remove,
mock_logging):
dbaas_base.clear_expired_password()
mysql_common_service.clear_expired_password()
self.assertEqual(1, mock_execute.call_count)
mock_remove.assert_not_called()
@patch.object(operating_system, 'read_file',
return_value={'client':
{'password': 'some password'}})
def test_get_auth_password(self, read_file_mock):
@patch.object(mysql_common_service.BaseMySqlApp.configuration_manager,
'get_value',
return_value=MagicMock({'get': 'some password'}))
def test_get_auth_password(self, get_cnf_mock, read_file_mock):
password = MySqlApp.get_auth_password()
read_file_mock.assert_called_once_with(MySqlApp.get_client_auth_file(),
codec=MySqlApp.CFG_CODEC)
self.assertEqual("some password", password)
@patch.object(mysql_common_service.BaseMySqlApp.configuration_manager,
'get_value',
side_effect=RuntimeError('Error'))
@patch.object(operating_system, 'read_file',
side_effect=RuntimeError('read_file error'))
def test_get_auth_password_error(self, _):
def test_get_auth_password_error(self, _, get_cnf_mock):
self.assertRaisesRegexp(RuntimeError, "read_file error",
MySqlApp.get_auth_password)
def test_service_discovery(self):
with patch.object(os.path, 'isfile', return_value=True):
mysql_service = \
dbaas_base.operating_system.service_discovery(["mysql"])
mysql_service = mysql_common_service.operating_system.\
service_discovery(["mysql"])
self.assertIsNotNone(mysql_service['cmd_start'])
self.assertIsNotNone(mysql_service['cmd_enable'])
@ -233,8 +239,9 @@ class DbaasTest(trove_testtools.TestCase):
"--tmpdir=/tmp --skip-external-locking"
with patch.object(os.path, 'isfile', return_value=True):
dbaas_base.utils.execute = Mock(return_value=(output, None))
options = dbaas_base.load_mysqld_options()
mysql_common_service.utils.execute = Mock(
return_value=(output, None))
options = mysql_common_service.load_mysqld_options()
self.assertEqual(5, len(options))
self.assertEqual(["mysql"], options["user"])
@ -249,8 +256,9 @@ class DbaasTest(trove_testtools.TestCase):
"--plugin-load=federated=ha_federated.so")
with patch.object(os.path, 'isfile', return_value=True):
dbaas_base.utils.execute = Mock(return_value=(output, None))
options = dbaas_base.load_mysqld_options()
mysql_common_service.utils.execute = Mock(
return_value=(output, None))
options = mysql_common_service.load_mysqld_options()
self.assertEqual(1, len(options))
self.assertEqual(["blackhole=ha_blackhole.so",
@ -260,9 +268,10 @@ class DbaasTest(trove_testtools.TestCase):
@patch.object(os.path, 'isfile', return_value=True)
def test_load_mysqld_options_error(self, mock_exists):
dbaas_base.utils.execute = Mock(side_effect=ProcessExecutionError())
mysql_common_service.utils.execute = Mock(
side_effect=ProcessExecutionError())
self.assertFalse(dbaas_base.load_mysqld_options())
self.assertFalse(mysql_common_service.load_mysqld_options())
class ResultSetStub(object):
@ -379,9 +388,9 @@ class MySqlAdminMockTest(trove_testtools.TestCase):
def tearDown(self):
super(MySqlAdminMockTest, self).tearDown()
@patch('trove.guestagent.datastore.mysql.service.MySqlApp'
'.get_auth_password', return_value='some_password')
def test_list_databases(self, auth_pwd_mock):
@patch.object(mysql_common_service.BaseMySqlApp, 'get_auth_password',
Mock(return_value='some_password'))
def test_list_databases(self):
with patch.object(self.mock_client, 'execute',
return_value=ResultSetStub(
[('db1', 'utf8', 'utf8_bin'),
@ -420,6 +429,9 @@ class MySqlAdminTest(trove_testtools.TestCase):
dbaas.MySqlApp.configuration_manager = Mock()
dbaas.orig_get_auth_password = dbaas.MySqlApp.get_auth_password
dbaas.MySqlApp.get_auth_password = Mock()
self.orig_configuration_manager = \
mysql_common_service.BaseMySqlApp.configuration_manager
mysql_common_service.BaseMySqlApp.configuration_manager = Mock()
self.mySqlAdmin = MySqlAdmin()
@ -431,6 +443,8 @@ class MySqlAdminTest(trove_testtools.TestCase):
dbaas.orig_configuration_manager
dbaas.MySqlApp.get_auth_password = \
dbaas.orig_get_auth_password
mysql_common_service.BaseMySqlApp.configuration_manager = \
self.orig_configuration_manager
super(MySqlAdminTest, self).tearDown()
def test__associate_dbs(self):
@ -587,9 +601,9 @@ class MySqlAdminTest(trove_testtools.TestCase):
self._assert_execute_call(access_grants_expected,
mock_execute, call_idx=1)
@patch('trove.guestagent.datastore.mysql.service.MySqlApp'
'.get_auth_password', return_value='some_password')
def test_list_databases(self, auth_pwd_mock):
@patch.object(mysql_common_service.BaseMySqlApp, 'get_auth_password',
Mock(return_value='some_password'))
def test_list_databases(self):
expected = ("SELECT schema_name as name,"
" default_character_set_name as charset,"
" default_collation_name as collation"
@ -798,13 +812,13 @@ class MySqlAppTest(trove_testtools.TestCase):
def setUp(self):
super(MySqlAppTest, self).setUp()
self.orig_utils_execute_with_timeout = \
dbaas_base.utils.execute_with_timeout
mysql_common_service.utils.execute_with_timeout
self.orig_time_sleep = time.sleep
self.orig_time_time = time.time
self.orig_unlink = os.unlink
self.orig_get_auth_password = MySqlApp.get_auth_password
self.orig_service_discovery = operating_system.service_discovery
mysql_app_patcher = patch.multiple(MySqlApp, get_engine=DEFAULT,
mysql_app_patcher = patch.multiple(mysql_common_service.BaseMySqlApp,
get_engine=DEFAULT,
get_auth_password=DEFAULT,
configuration_manager=DEFAULT)
self.addCleanup(mysql_app_patcher.stop)
@ -819,7 +833,7 @@ class MySqlAppTest(trove_testtools.TestCase):
'cmd_stop': Mock(),
'cmd_enable': Mock(),
'cmd_disable': Mock(),
'cmd_bootstrap_pxc_cluster': Mock(),
'cmd_bootstrap_galera_cluster': Mock(),
'bin': Mock()}
operating_system.service_discovery = Mock(
return_value=mysql_service)
@ -834,7 +848,7 @@ class MySqlAppTest(trove_testtools.TestCase):
self.orig_create_engine = sqlalchemy.create_engine
def tearDown(self):
dbaas_base.utils.execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
time.sleep = self.orig_time_sleep
time.time = self.orig_time_time
@ -877,7 +891,7 @@ class MySqlAppTest(trove_testtools.TestCase):
def test_stop_mysql(self):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.appStatus.set_next_status(
rd_instance.ServiceStatuses.SHUTDOWN)
@ -888,7 +902,7 @@ class MySqlAppTest(trove_testtools.TestCase):
def test_stop_mysql_with_db_update(self):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.appStatus.set_next_status(
rd_instance.ServiceStatuses.SHUTDOWN)
@ -918,7 +932,7 @@ class MySqlAppTest(trove_testtools.TestCase):
@patch('trove.guestagent.datastore.service.LOG')
@patch('trove.guestagent.datastore.mysql_common.service.LOG')
def test_stop_mysql_error(self, *args):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
self.mySqlApp.state_change_wait_time = 1
with patch.object(BaseDbStatus, 'prepare_completed') as patch_pc:
@ -975,14 +989,14 @@ class MySqlAppTest(trove_testtools.TestCase):
def test_wipe_ib_logfiles_error(self, get_datadir_mock, mock_logging):
mocked = Mock(side_effect=ProcessExecutionError('Error'))
dbaas_base.utils.execute_with_timeout = mocked
mysql_common_service.utils.execute_with_timeout = mocked
self.assertRaises(ProcessExecutionError,
self.mySqlApp.wipe_ib_logfiles)
def test_start_mysql(self):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
self.mySqlApp._enable_mysql_on_boot = Mock()
self.mySqlApp.start_mysql()
@ -990,7 +1004,7 @@ class MySqlAppTest(trove_testtools.TestCase):
def test_start_mysql_with_db_update(self):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.mySqlApp._enable_mysql_on_boot = Mock()
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
@ -1006,7 +1020,7 @@ class MySqlAppTest(trove_testtools.TestCase):
@patch('trove.guestagent.datastore.service.LOG')
def test_start_mysql_runs_forever(self, *args):
dbaas_base.utils.execute_with_timeout = Mock()
mysql_common_service.utils.execute_with_timeout = Mock()
self.mySqlApp._enable_mysql_on_boot = Mock()
self.mySqlApp.state_change_wait_time = 1
self.appStatus.set_next_status(rd_instance.ServiceStatuses.SHUTDOWN)
@ -1025,7 +1039,7 @@ class MySqlAppTest(trove_testtools.TestCase):
self.mySqlApp._enable_mysql_on_boot = Mock()
mocked = Mock(side_effect=ProcessExecutionError('Error'))
dbaas_base.utils.execute_with_timeout = mocked
mysql_common_service.utils.execute_with_timeout = mocked
with patch.object(BaseDbStatus, 'prepare_completed') as patch_pc:
patch_pc.__get__ = Mock(return_value=True)
@ -1060,8 +1074,8 @@ class MySqlAppTest(trove_testtools.TestCase):
self.mySqlApp.reset_configuration(configuration=configuration)
cfg_reset.assert_called_once_with('some junk')
@patch.object(dbaas.MySqlApp, 'get_auth_password',
return_value='some_password')
@patch.object(dbaas.MySqlApp,
'get_auth_password', return_value='some_password')
def test_reset_configuration(self, auth_pwd_mock):
save_cfg_mock = Mock()
save_auth_mock = Mock()
@ -1077,12 +1091,13 @@ class MySqlAppTest(trove_testtools.TestCase):
save_cfg_mock.assert_called_once_with('some junk')
save_auth_mock.assert_called_once_with(
auth_pwd_mock.return_value)
wipe_ib_mock.assert_called_once_with()
@patch.object(utils, 'execute_with_timeout', return_value=('0', ''))
def test__enable_mysql_on_boot(self, mock_execute):
mysql_service = \
dbaas_base.operating_system.service_discovery(["mysql"])
mysql_common_service.operating_system.service_discovery(["mysql"])
self.mySqlApp._enable_mysql_on_boot()
self.assertEqual(1, mock_execute.call_count)
mock_execute.assert_called_with(mysql_service['cmd_enable'],
@ -1101,7 +1116,7 @@ class MySqlAppTest(trove_testtools.TestCase):
@patch.object(utils, 'execute_with_timeout', return_value=('0', ''))
def test__disable_mysql_on_boot(self, mock_execute):
mysql_service = \
dbaas_base.operating_system.service_discovery(["mysql"])
mysql_common_service.operating_system.service_discovery(["mysql"])
self.mySqlApp._disable_mysql_on_boot()
self.assertEqual(1, mock_execute.call_count)
mock_execute.assert_called_with(mysql_service['cmd_disable'],
@ -1134,27 +1149,29 @@ class MySqlAppTest(trove_testtools.TestCase):
with patch.object(self.mySqlApp.configuration_manager,
'apply_system_override') as apply_sys_mock:
self.mySqlApp.write_replication_source_overrides('something')
apply_sys_mock.assert_called_once_with('something',
dbaas_base.CNF_MASTER)
apply_sys_mock.assert_called_once_with(
'something', mysql_common_service.CNF_MASTER)
def test_write_replication_replica_overrides(self):
with patch.object(self.mySqlApp.configuration_manager,
'apply_system_override') as apply_sys_mock:
self.mySqlApp.write_replication_replica_overrides('something')
apply_sys_mock.assert_called_once_with('something',
dbaas_base.CNF_SLAVE)
apply_sys_mock.assert_called_once_with(
'something', mysql_common_service.CNF_SLAVE)
def test_remove_replication_source_overrides(self):
with patch.object(self.mySqlApp.configuration_manager,
'remove_system_override') as remove_sys_mock:
self.mySqlApp.remove_replication_source_overrides()
remove_sys_mock.assert_called_once_with(dbaas_base.CNF_MASTER)
remove_sys_mock.assert_called_once_with(
mysql_common_service.CNF_MASTER)
def test_remove_replication_replica_overrides(self):
with patch.object(self.mySqlApp.configuration_manager,
'remove_system_override') as remove_sys_mock:
self.mySqlApp.remove_replication_replica_overrides()
remove_sys_mock.assert_called_once_with(dbaas_base.CNF_SLAVE)
remove_sys_mock.assert_called_once_with(
mysql_common_service.CNF_SLAVE)
def test_exists_replication_source_overrides(self):
with patch.object(self.mySqlApp.configuration_manager,
@ -1369,12 +1386,12 @@ class MySqlAppTest(trove_testtools.TestCase):
MySqlApp.get_client_auth_file(),
{'client': {'host': '127.0.0.1',
'password': 'some_password',
'user': dbaas_base.ADMIN_USER_NAME}},
'user': mysql_common_service.ADMIN_USER_NAME}},
codec=MySqlApp.CFG_CODEC)
@patch.object(utils, 'generate_random_password',
return_value='some_password')
@patch.object(dbaas_base, 'clear_expired_password')
@patch.object(mysql_common_service, 'clear_expired_password')
def test_secure(self, clear_pwd_mock, auth_pwd_mock):
self.mySqlApp.start_mysql = Mock()
@ -1471,7 +1488,7 @@ class MySqlAppTest(trove_testtools.TestCase):
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
@patch.object(dbaas_base, 'clear_expired_password')
@patch.object(mysql_common_service, 'clear_expired_password')
def test_secure_write_conf_error(self, clear_pwd_mock):
self.mySqlApp.start_mysql = Mock()
@ -1538,7 +1555,7 @@ class MySqlAppMockTest(trove_testtools.TestCase):
utils.execute_with_timeout = self.orig_utils_execute_with_timeout
super(MySqlAppMockTest, self).tearDown()
@patch.object(dbaas_base, 'clear_expired_password')
@patch.object(mysql_common_service, 'clear_expired_password')
@patch.object(utils, 'generate_random_password',
return_value='some_password')
def test_secure_keep_root(self, auth_pwd_mock, clear_pwd_mock):
@ -1561,9 +1578,9 @@ class MySqlAppMockTest(trove_testtools.TestCase):
app._reset_configuration.assert_has_calls(reset_config_calls)
self.assertTrue(mock_execute.called)
@patch.object(dbaas_base, 'clear_expired_password')
@patch('trove.guestagent.datastore.mysql.service.MySqlApp'
'.get_auth_password', return_value='some_password')
@patch.object(mysql_common_service, 'clear_expired_password')
@patch.object(mysql_common_service.BaseMySqlApp,
'get_auth_password', return_value='some_password')
def test_secure_with_mycnf_error(self, auth_pwd_mock, clear_pwd_mock):
with patch.object(self.mock_client,
'execute', return_value=None) as mock_execute:
@ -1576,10 +1593,10 @@ class MySqlAppMockTest(trove_testtools.TestCase):
mock_status = MagicMock()
mock_status.wait_for_real_status_to_change_to = MagicMock(
return_value=True)
dbaas_base.clear_expired_password = \
mysql_common_service.clear_expired_password = \
MagicMock(return_value=None)
app = MySqlApp(mock_status)
dbaas_base.clear_expired_password = \
mysql_common_service.clear_expired_password = \
MagicMock(return_value=None)
self.assertRaises(RuntimeError, app.secure, None)
self.assertTrue(mock_execute.called)
@ -1618,25 +1635,25 @@ class MySqlRootStatusTest(trove_testtools.TestCase):
utils.execute_with_timeout = self.orig_utils_execute_with_timeout
super(MySqlRootStatusTest, self).tearDown()
@patch.object(dbaas.MySqlApp, 'get_auth_password',
return_value='some_password')
@patch.object(mysql_common_service.BaseMySqlApp,
'get_auth_password', return_value='some_password')
def test_root_is_enabled(self, auth_pwd_mock):
mock_rs = MagicMock()
mock_rs.rowcount = 1
with patch.object(self.mock_client, 'execute', return_value=mock_rs):
self.assertTrue(MySqlRootAccess().is_root_enabled())
@patch.object(dbaas.MySqlApp, 'get_auth_password',
return_value='some_password')
@patch.object(mysql_common_service.BaseMySqlApp,
'get_auth_password', return_value='some_password')
def test_root_is_not_enabled(self, auth_pwd_mock):
mock_rs = MagicMock()
mock_rs.rowcount = 0
with patch.object(self.mock_client, 'execute', return_value=mock_rs):
self.assertFalse(MySqlRootAccess().is_root_enabled())
@patch.object(dbaas_base, 'clear_expired_password')
@patch.object(dbaas.MySqlApp, 'get_auth_password',
return_value='some_password')
@patch.object(mysql_common_service, 'clear_expired_password')
@patch.object(mysql_common_service.BaseMySqlApp,
'get_auth_password', return_value='some_password')
def test_enable_root(self, auth_pwd_mock, clear_pwd_mock):
with patch.object(self.mock_client,
'execute', return_value=None) as mock_execute:
@ -1846,12 +1863,12 @@ class KeepAliveConnectionTest(trove_testtools.TestCase):
def setUp(self):
super(KeepAliveConnectionTest, self).setUp()
self.orig_utils_execute_with_timeout = \
dbaas_base.utils.execute_with_timeout
mysql_common_service.utils.execute_with_timeout
self.orig_LOG_err = dbaas.LOG
def tearDown(self):
super(KeepAliveConnectionTest, self).tearDown()
dbaas_base.utils.execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
dbaas.LOG = self.orig_LOG_err
@ -2223,9 +2240,11 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
super(MySqlAppStatusTest, self).setUp()
util.init_db()
self.orig_utils_execute_with_timeout = \
dbaas_base.utils.execute_with_timeout
self.orig_load_mysqld_options = dbaas_base.load_mysqld_options
self.orig_dbaas_base_os_path_exists = dbaas_base.os.path.exists
mysql_common_service.utils.execute_with_timeout
self.orig_load_mysqld_options = \
mysql_common_service.load_mysqld_options
self.orig_mysql_common_service_os_path_exists = \
mysql_common_service.os.path.exists
self.orig_dbaas_time_sleep = time.sleep
self.orig_time_time = time.time
self.FAKE_ID = str(uuid4())
@ -2234,10 +2253,12 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
dbaas.CONF.guest_id = self.FAKE_ID
def tearDown(self):
dbaas_base.utils.execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
dbaas_base.load_mysqld_options = self.orig_load_mysqld_options
dbaas_base.os.path.exists = self.orig_dbaas_base_os_path_exists
mysql_common_service.load_mysqld_options = \
self.orig_load_mysqld_options
mysql_common_service.os.path.exists = \
self.orig_mysql_common_service_os_path_exists
time.sleep = self.orig_dbaas_time_sleep
time.time = self.orig_time_time
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
@ -2246,7 +2267,8 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
def test_get_actual_db_status(self):
dbaas_base.utils.execute_with_timeout = Mock(return_value=(None, None))
mysql_common_service.utils.execute_with_timeout = \
Mock(return_value=(None, None))
self.mySqlAppStatus = MySqlAppStatus.get()
status = self.mySqlAppStatus._get_actual_db_status()
@ -2260,7 +2282,7 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
def test_get_actual_db_status_error_crashed(self, mock_logging,
mock_exists,
mock_execute):
dbaas_base.load_mysqld_options = Mock(return_value={})
mysql_common_service.load_mysqld_options = Mock(return_value={})
self.mySqlAppStatus = MySqlAppStatus.get()
status = self.mySqlAppStatus._get_actual_db_status()
self.assertEqual(rd_instance.ServiceStatuses.CRASHED, status)
@ -2269,9 +2291,9 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
def test_get_actual_db_status_error_shutdown(self, *args):
mocked = Mock(side_effect=ProcessExecutionError())
dbaas_base.utils.execute_with_timeout = mocked
dbaas_base.load_mysqld_options = Mock(return_value={})
dbaas_base.os.path.exists = Mock(return_value=False)
mysql_common_service.utils.execute_with_timeout = mocked
mysql_common_service.load_mysqld_options = Mock(return_value={})
mysql_common_service.os.path.exists = Mock(return_value=False)
self.mySqlAppStatus = MySqlAppStatus.get()
status = self.mySqlAppStatus._get_actual_db_status()
@ -2281,10 +2303,10 @@ class MySqlAppStatusTest(trove_testtools.TestCase):
@patch('trove.guestagent.datastore.mysql_common.service.LOG')
def test_get_actual_db_status_error_blocked(self, *args):
dbaas_base.utils.execute_with_timeout = MagicMock(
mysql_common_service.utils.execute_with_timeout = MagicMock(
side_effect=[ProcessExecutionError(), ("some output", None)])
dbaas_base.load_mysqld_options = Mock()
dbaas_base.os.path.exists = Mock(return_value=True)
mysql_common_service.load_mysqld_options = Mock()
mysql_common_service.os.path.exists = Mock(return_value=True)
self.mySqlAppStatus = MySqlAppStatus.get()
status = self.mySqlAppStatus._get_actual_db_status()
@ -3427,52 +3449,56 @@ class PXCAppTest(trove_testtools.TestCase):
def setUp(self):
super(PXCAppTest, self).setUp()
self.orig_utils_execute_with_timeout = \
dbaas_base.utils.execute_with_timeout
mysql_common_service.utils.execute_with_timeout
self.orig_time_sleep = time.sleep
self.orig_time_time = time.time
self.orig_unlink = os.unlink
self.orig_get_auth_password = pxc_service.PXCApp.get_auth_password
self.orig_pxc_system_service_discovery = pxc_system.service_discovery
self.orig_get_auth_password = \
mysql_common_service.BaseMySqlApp.get_auth_password
self.FAKE_ID = str(uuid4())
InstanceServiceStatus.create(instance_id=self.FAKE_ID,
status=rd_instance.ServiceStatuses.NEW)
self.appStatus = FakeAppStatus(self.FAKE_ID,
rd_instance.ServiceStatuses.NEW)
self.PXCApp = pxc_service.PXCApp(self.appStatus)
mysql_service = {'cmd_start': Mock(),
'cmd_stop': Mock(),
'cmd_enable': Mock(),
'cmd_disable': Mock(),
'cmd_bootstrap_pxc_cluster': Mock(),
'bin': Mock()}
pxc_system.service_discovery = Mock(
return_value=mysql_service)
mysql_service = patch.object(
pxc_service.PXCApp, 'mysql_service',
PropertyMock(return_value={
'cmd_start': Mock(),
'cmd_stop': Mock(),
'cmd_enable': Mock(),
'cmd_disable': Mock(),
'cmd_bootstrap_galera_cluster': Mock(),
'bin': Mock()
}))
mysql_service.start()
self.addCleanup(mysql_service.stop)
time.sleep = Mock()
time.time = Mock(side_effect=faketime)
os.unlink = Mock()
pxc_service.PXCApp.get_auth_password = Mock()
mysql_common_service.BaseMySqlApp.get_auth_password = Mock()
self.mock_client = Mock()
self.mock_execute = Mock()
self.mock_client.__enter__ = Mock()
self.mock_client.__exit__ = Mock()
self.mock_client.__enter__.return_value.execute = self.mock_execute
pxc_service.orig_configuration_manager = (
pxc_service.PXCApp.configuration_manager)
pxc_service.PXCApp.configuration_manager = Mock()
self.orig_configuration_manager = \
mysql_common_service.BaseMySqlApp.configuration_manager
mysql_common_service.BaseMySqlApp.configuration_manager = Mock()
self.orig_create_engine = sqlalchemy.create_engine
def tearDown(self):
self.PXCApp = None
dbaas_base.utils.execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
time.sleep = self.orig_time_sleep
time.time = self.orig_time_time
os.unlink = self.orig_unlink
pxc_system.service_discovery = self.orig_pxc_system_service_discovery
pxc_service.PXCApp.get_auth_password = self.orig_get_auth_password
mysql_common_service.BaseMySqlApp.get_auth_password = \
self.orig_get_auth_password
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
pxc_service.PXCApp.configuration_manager = \
pxc_service.orig_configuration_manager
mysql_common_service.BaseMySqlApp.configuration_manager = \
self.orig_configuration_manager
sqlalchemy.create_engine = self.orig_create_engine
super(PXCAppTest, self).tearDown()
@ -3494,11 +3520,11 @@ class PXCAppTest(trove_testtools.TestCase):
@patch.object(utils, 'execute_with_timeout', return_value=('0', ''))
def test__bootstrap_cluster(self, mock_execute):
pxc_service_cmds = pxc_system.service_discovery(['mysql'])
pxc_service_cmds = self.PXCApp.mysql_service
self.PXCApp._bootstrap_cluster(timeout=20)
self.assertEqual(1, mock_execute.call_count)
mock_execute.assert_called_with(
pxc_service_cmds['cmd_bootstrap_pxc_cluster'],
pxc_service_cmds['cmd_bootstrap_galera_cluster'],
shell=True,
timeout=20)
@ -3541,6 +3567,131 @@ class PXCAppTest(trove_testtools.TestCase):
self.assertEqual(1, self.PXCApp._bootstrap_cluster.call_count)
class MariaDBAppTest(trove_testtools.TestCase):
def setUp(self):
super(MariaDBAppTest, self).setUp()
self.orig_utils_execute_with_timeout = \
mysql_common_service.utils.execute_with_timeout
self.orig_time_sleep = time.sleep
self.orig_time_time = time.time
self.orig_unlink = os.unlink
self.orig_get_auth_password = \
mysql_common_service.BaseMySqlApp.get_auth_password
self.FAKE_ID = str(uuid4())
InstanceServiceStatus.create(instance_id=self.FAKE_ID,
status=rd_instance.ServiceStatuses.NEW)
self.appStatus = FakeAppStatus(self.FAKE_ID,
rd_instance.ServiceStatuses.NEW)
self.MariaDBApp = mariadb_service.MariaDBApp(self.appStatus)
mysql_service = patch.object(
mariadb_service.MariaDBApp, 'mysql_service',
PropertyMock(return_value={
'cmd_start': Mock(),
'cmd_stop': Mock(),
'cmd_enable': Mock(),
'cmd_disable': Mock(),
'cmd_bootstrap_galera_cluster': Mock(),
'bin': Mock()
}))
mysql_service.start()
self.addCleanup(mysql_service.stop)
time.sleep = Mock()
time.time = Mock(side_effect=faketime)
os.unlink = Mock()
mysql_common_service.BaseMySqlApp.get_auth_password = Mock()
self.mock_client = Mock()
self.mock_execute = Mock()
self.mock_client.__enter__ = Mock()
self.mock_client.__exit__ = Mock()
self.mock_client.__enter__.return_value.execute = self.mock_execute
self.orig_configuration_manager = \
mysql_common_service.BaseMySqlApp.configuration_manager
mysql_common_service.BaseMySqlApp.configuration_manager = Mock()
self.orig_create_engine = sqlalchemy.create_engine
def tearDown(self):
self.MariaDBApp = None
mysql_common_service.utils.execute_with_timeout = \
self.orig_utils_execute_with_timeout
time.sleep = self.orig_time_sleep
time.time = self.orig_time_time
os.unlink = self.orig_unlink
mysql_common_service.BaseMySqlApp.get_auth_password = \
self.orig_get_auth_password
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
mysql_common_service.BaseMySqlApp.configuration_manager = \
self.orig_configuration_manager
sqlalchemy.create_engine = self.orig_create_engine
super(MariaDBAppTest, self).tearDown()
@patch.object(mariadb_service.MariaDBApp, 'get_engine',
return_value=MagicMock(name='get_engine'))
def test__grant_cluster_replication_privilege(self, mock_engine):
repl_user = {
'name': 'test-user',
'password': 'test-user-password',
}
with patch.object(mariadb_service.MariaDBApp, 'local_sql_client',
return_value=self.mock_client):
self.MariaDBApp._grant_cluster_replication_privilege(repl_user)
args, _ = self.mock_execute.call_args_list[0]
expected = ("GRANT LOCK TABLES, RELOAD, REPLICATION CLIENT ON *.* "
"TO `test-user`@`%` IDENTIFIED BY 'test-user-password';")
self.assertEqual(expected, args[0].text,
"Sql statements are not the same")
@patch.object(utils, 'execute_with_timeout', return_value=('0', ''))
def test__bootstrap_cluster(self, mock_execute):
mariadb_service_cmds = self.MariaDBApp.mysql_service
self.MariaDBApp._bootstrap_cluster(timeout=20)
self.assertEqual(1, mock_execute.call_count)
mock_execute.assert_called_with(
mariadb_service_cmds['cmd_bootstrap_galera_cluster'],
shell=True,
timeout=20)
def test_install_cluster(self):
repl_user = {
'name': 'test-user',
'password': 'test-user-password',
}
apply_mock = Mock()
self.MariaDBApp.configuration_manager.apply_system_override = \
apply_mock
self.MariaDBApp.stop_db = Mock()
self.MariaDBApp._grant_cluster_replication_privilege = Mock()
self.MariaDBApp.wipe_ib_logfiles = Mock()
self.MariaDBApp.start_mysql = Mock()
self.MariaDBApp.install_cluster(repl_user, "something")
self.assertEqual(1, self.MariaDBApp.stop_db.call_count)
self.assertEqual(
1, self.MariaDBApp._grant_cluster_replication_privilege.call_count)
self.assertEqual(1, apply_mock.call_count)
self.assertEqual(1, self.MariaDBApp.wipe_ib_logfiles.call_count)
self.assertEqual(1, self.MariaDBApp.start_mysql.call_count)
def test_install_cluster_with_bootstrap(self):
repl_user = {
'name': 'test-user',
'password': 'test-user-password',
}
apply_mock = Mock()
self.MariaDBApp.configuration_manager.apply_system_override = \
apply_mock
self.MariaDBApp.stop_db = Mock()
self.MariaDBApp._grant_cluster_replication_privilege = Mock()
self.MariaDBApp.wipe_ib_logfiles = Mock()
self.MariaDBApp._bootstrap_cluster = Mock()
self.MariaDBApp.install_cluster(repl_user, "something", bootstrap=True)
self.assertEqual(1, self.MariaDBApp.stop_db.call_count)
self.assertEqual(
1, self.MariaDBApp._grant_cluster_replication_privilege.call_count)
self.assertEqual(1, self.MariaDBApp.wipe_ib_logfiles.call_count)
self.assertEqual(1, apply_mock.call_count)
self.assertEqual(1, self.MariaDBApp._bootstrap_cluster.call_count)
class PostgresAppTest(BaseAppTest.AppTestCase):
class FakePostgresApp(pg_manager.Manager):

View File

@ -18,8 +18,8 @@ import mock
import trove.common.context as context
from trove.common import exception
from trove.common.rpc.version import RPC_API_VERSION
from trove.common.strategies.cluster.experimental.pxc.guestagent import (
PXCGuestAgentAPI)
from trove.common.strategies.cluster.experimental.galera_common.guestagent \
import GaleraCommonGuestAgentStrategy
from trove import rpc
from trove.tests.unittests import trove_testtools
@ -39,10 +39,12 @@ class ApiTest(trove_testtools.TestCase):
@mock.patch.object(rpc, 'get_client')
def setUp(self, *args):
super(ApiTest, self).setUp()
cluster_guest_api = (GaleraCommonGuestAgentStrategy()
.guest_client_class)
self.context = context.TroveContext()
self.guest = PXCGuestAgentAPI(self.context, 0)
self.guest = cluster_guest_api(self.context, 0)
self.guest._call = _mock_call
self.api = PXCGuestAgentAPI(self.context, "instance-id-x23d2d")
self.api = cluster_guest_api(self.context, "instance-id-x23d2d")
self._mock_rpc_client()
def test_get_routing_key(self):

View File

@ -0,0 +1,123 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import MagicMock
from mock import patch
from trove.common.context import TroveContext
from trove.guestagent.datastore.galera_common import manager as galera_manager
from trove.guestagent.datastore.galera_common import service as galera_service
from trove.guestagent.datastore.mysql_common import service as mysql_service
from trove.tests.unittests import trove_testtools
class GaleraTestApp(galera_service.GaleraApp):
def __init__(self, status):
super(GaleraTestApp, self).__init__(
status, mysql_service.BaseLocalSqlClient,
mysql_service.BaseKeepAliveConnection)
@property
def cluster_configuration(self):
return self.configuration_manager.get_value('mysqld')
class GaleraTestRootAccess(mysql_service.BaseMySqlRootAccess):
def __init__(self):
super(GaleraTestRootAccess, self).__init__(
mysql_service.BaseLocalSqlClient,
GaleraTestApp(mysql_service.BaseMySqlAppStatus.get()))
class GaleraTestAdmin(mysql_service.BaseMySqlAdmin):
def __init__(self):
super(GaleraTestAdmin, self).__init__(
mysql_service.BaseLocalSqlClient, GaleraTestRootAccess(),
GaleraTestApp)
class GuestAgentManagerTest(trove_testtools.TestCase):
def setUp(self):
super(GuestAgentManagerTest, self).setUp()
self.manager = galera_manager.GaleraManager(
GaleraTestApp, mysql_service.BaseMySqlAppStatus,
GaleraTestAdmin)
self.context = TroveContext()
patcher_rs = patch(
'trove.guestagent.strategies.replication.get_instance')
patcher_rs.start()
self.addCleanup(patcher_rs.stop)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(galera_service.GaleraApp, 'install_cluster',
new_callable=MagicMock)
def test_install_cluster(self, install_cluster, app_status_get):
install_cluster.return_value = MagicMock()
app_status_get.return_value = None
replication_user = "repuser"
configuration = "configuration"
bootstrap = True
self.manager.install_cluster(self.context, replication_user,
configuration, bootstrap)
app_status_get.assert_any_call()
install_cluster.assert_called_with(
replication_user, configuration, bootstrap)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(galera_service.GaleraApp, 'reset_admin_password',
new_callable=MagicMock)
def test_reset_admin_password(self, reset_admin_password, app_status_get):
reset_admin_password.return_value = None
app_status_get.return_value = MagicMock()
admin_password = "password"
self.manager.reset_admin_password(self.context, admin_password)
app_status_get.assert_any_call()
reset_admin_password.assert_called_with(admin_password)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(galera_service.GaleraApp, 'get_cluster_context')
def test_get_cluster_context(self, get_cluster_ctxt, app_status_get):
get_cluster_ctxt.return_value = {'cluster': 'info'}
self.manager.get_cluster_context(self.context)
app_status_get.assert_any_call()
get_cluster_ctxt.assert_any_call()
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(galera_service.GaleraApp,
'write_cluster_configuration_overrides')
def test_write_cluster_configuration_overrides(self, conf_overries,
app_status_get):
cluster_configuration = "cluster_configuration"
self.manager.write_cluster_configuration_overrides(
self.context, cluster_configuration)
app_status_get.assert_any_call()
conf_overries.assert_called_with(cluster_configuration)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(mysql_service.BaseMySqlAdmin, 'enable_root')
def test_enable_root_with_password(self, reset_admin_pwd,
app_status_get):
admin_password = "password"
self.manager.enable_root_with_password(self.context, admin_password)
reset_admin_pwd.assert_called_with(admin_password)

View File

@ -0,0 +1,66 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import MagicMock
from mock import patch
import testtools
from trove.common.context import TroveContext
from trove.guestagent.datastore.experimental.mariadb import (
manager as mariadb_manager)
from trove.guestagent.datastore.experimental.mariadb import (
service as mariadb_service)
from trove.guestagent.datastore.mysql_common import service as mysql_service
class GuestAgentManagerTest(testtools.TestCase):
def setUp(self):
super(GuestAgentManagerTest, self).setUp()
self.manager = mariadb_manager.Manager()
self.context = TroveContext()
patcher_rs = patch(
'trove.guestagent.strategies.replication.get_instance')
patcher_rs.start()
self.addCleanup(patcher_rs.stop)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(mariadb_service.MariaDBApp, 'install_cluster',
new_callable=MagicMock)
def test_install_cluster(self, install_cluster, app_status_get):
install_cluster.return_value = MagicMock()
app_status_get.return_value = None
replication_user = "repuser"
configuration = "configuration"
bootstrap = True
self.manager.install_cluster(self.context, replication_user,
configuration, bootstrap)
app_status_get.assert_any_call()
install_cluster.assert_called_with(
replication_user, configuration, bootstrap)
@patch.object(mysql_service.BaseMySqlAppStatus, 'get',
new_callable=MagicMock)
@patch.object(mariadb_service.MariaDBApp, 'reset_admin_password',
new_callable=MagicMock)
def test_reset_admin_password(self, reset_admin_password, app_status_get):
reset_admin_password.return_value = None
app_status_get.return_value = MagicMock()
admin_password = "password"
self.manager.reset_admin_password(self.context, admin_password)
app_status_get.assert_any_call()
reset_admin_password.assert_called_with(admin_password)

View File

@ -1,80 +0,0 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import Mock
from mock import patch
from trove.guestagent.datastore.experimental.pxc.manager import Manager
import trove.guestagent.datastore.experimental.pxc.service as dbaas
import trove.guestagent.datastore.mysql_common.service as mysql_common
from trove.tests.unittests import trove_testtools
class GuestAgentManagerTest(trove_testtools.TestCase):
def setUp(self):
super(GuestAgentManagerTest, self).setUp()
self.manager = Manager()
self.context = trove_testtools.TroveTestContext(self)
self.patcher_rs = patch(
'trove.guestagent.strategies.replication.get_instance')
self.mock_rs_class = self.patcher_rs.start()
status_patcher = patch.object(dbaas.PXCAppStatus, 'get',
return_value=Mock())
self.addCleanup(status_patcher.stop)
self.status_get_mock = status_patcher.start()
def tearDown(self):
super(GuestAgentManagerTest, self).tearDown()
self.patcher_rs.stop()
@patch.object(dbaas.PXCApp, 'install_cluster')
def test_install_cluster(self, install_cluster_mock):
replication_user = "repuser"
configuration = "configuration"
bootstrap = True
self.manager.install_cluster(self.context, replication_user,
configuration, bootstrap)
self.status_get_mock.assert_any_call()
install_cluster_mock.assert_called_with(
replication_user, configuration, bootstrap)
@patch.object(dbaas.PXCApp, 'reset_admin_password')
def test_reset_admin_password(self, reset_admin_pwd):
admin_password = "password"
self.manager.reset_admin_password(self.context, admin_password)
self.status_get_mock.assert_any_call()
reset_admin_pwd.assert_called_with(admin_password)
@patch.object(dbaas.PXCApp, 'get_cluster_context')
def test_get_cluster_context(self, get_cluster_ctxt):
get_cluster_ctxt.return_value = {'cluster': 'info'}
self.manager.get_cluster_context(self.context)
self.status_get_mock.assert_any_call()
get_cluster_ctxt.assert_any_call()
@patch.object(dbaas.PXCApp, 'write_cluster_configuration_overrides')
def test_write_cluster_configuration_overrides(self, conf_overries):
cluster_configuration = "cluster_configuration"
self.manager.write_cluster_configuration_overrides(
self.context, cluster_configuration)
self.status_get_mock.assert_any_call()
conf_overries.assert_called_with(cluster_configuration)
@patch.object(mysql_common.BaseMySqlAdmin, 'enable_root')
def test_enable_root_with_password(self, reset_admin_pwd):
admin_password = "password"
self.manager.enable_root_with_password(self.context, admin_password)
reset_admin_pwd.assert_called_with(admin_password)

View File

@ -18,11 +18,11 @@ from mock import patch
from trove.cluster.models import ClusterTasks as ClusterTaskStatus
from trove.cluster.models import DBCluster
from trove.common import exception
from trove.common.strategies.cluster.experimental.pxc.taskmanager import (
PXCClusterTasks as ClusterTasks)
from trove.common.strategies.cluster.experimental.pxc.taskmanager import (
PXCTaskManagerStrategy as task_strategy)
from trove.common.exception import GuestError
from trove.common.strategies.cluster.experimental.galera_common.taskmanager \
import GaleraCommonClusterTasks
from trove.common.strategies.cluster.experimental.galera_common.taskmanager \
import GaleraCommonTaskManagerStrategy
from trove.datastore import models as datastore_models
from trove.instance.models import BaseInstance
from trove.instance.models import DBInstance
@ -34,9 +34,9 @@ from trove.tests.unittests import trove_testtools
from trove.tests.unittests.util import util
class PXCClusterTasksTest(trove_testtools.TestCase):
class GaleraClusterTasksTest(trove_testtools.TestCase):
def setUp(self):
super(PXCClusterTasksTest, self).setUp()
super(GaleraClusterTasksTest, self).setUp()
util.init_db()
self.cluster_id = "1232"
self.cluster_name = "Cluster-1234"
@ -78,10 +78,9 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
mock_ds1.name = 'pxc'
mock_dv1 = Mock()
mock_dv1.name = '7.1'
self.clustertasks = ClusterTasks(Mock(),
self.db_cluster,
datastore=mock_ds1,
datastore_version=mock_dv1)
self.clustertasks = GaleraCommonClusterTasks(
Mock(), self.db_cluster, datastore=mock_ds1,
datastore_version=mock_dv1)
self.cluster_context = {
'replication_user': {
'name': "name",
@ -91,7 +90,7 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
'admin_password': "admin_password"
}
@patch.object(ClusterTasks, 'update_statuses_on_failure')
@patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
@patch.object(InstanceServiceStatus, 'find_by')
@patch('trove.taskmanager.models.LOG')
def test_all_instances_ready_bad_status(self, mock_logging,
@ -111,17 +110,16 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
self.cluster_id)
self.assertTrue(ret_val)
@patch('trove.common.strategies.cluster.experimental.pxc.taskmanager.LOG')
@patch.object(ClusterTasks, 'update_statuses_on_failure')
@patch.object(ClusterTasks, '_all_instances_ready', return_value=False)
@patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
@patch.object(GaleraCommonClusterTasks, '_all_instances_ready',
return_value=False)
@patch.object(Instance, 'load')
@patch.object(DBInstance, 'find_all')
@patch.object(datastore_models.Datastore, 'load')
@patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
def test_create_cluster_instance_not_ready(self, mock_dv, mock_ds,
mock_find_all, mock_load,
mock_ready, mock_update,
mock_logging):
mock_ready, mock_update):
mock_find_all.return_value.all.return_value = [self.dbinst1]
mock_load.return_value = BaseInstance(Mock(),
self.dbinst1, Mock(),
@ -130,15 +128,16 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
self.clustertasks.create_cluster(Mock(), self.cluster_id)
mock_update.assert_called_with(self.cluster_id)
@patch.object(ClusterTasks, 'update_statuses_on_failure')
@patch.object(ClusterTasks, 'reset_task')
@patch.object(ClusterTasks, 'get_ip')
@patch.object(ClusterTasks, '_all_instances_ready')
@patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
@patch.object(GaleraCommonClusterTasks, 'reset_task')
@patch.object(GaleraCommonClusterTasks, 'get_ip')
@patch.object(GaleraCommonClusterTasks, '_all_instances_ready')
@patch.object(Instance, 'load')
@patch.object(DBInstance, 'find_all')
@patch.object(datastore_models.Datastore, 'load')
@patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
@patch('trove.common.strategies.cluster.experimental.pxc.taskmanager.LOG')
@patch('trove.common.strategies.cluster.experimental.galera_common.'
'taskmanager.LOG')
def test_create_cluster_fail(self, mock_logging, mock_dv, mock_ds,
mock_find_all, mock_load, mock_ready, mock_ip,
mock_reset_task, mock_update_status):
@ -149,16 +148,16 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
ServiceStatuses.NEW))
mock_ip.return_value = "10.0.0.2"
guest_client = Mock()
guest_client.install_cluster = Mock(
side_effect=exception.GuestError("Error"))
with patch.object(ClusterTasks, 'get_guest',
guest_client.install_cluster = Mock(side_effect=GuestError("Error"))
with patch.object(GaleraCommonClusterTasks, 'get_guest',
return_value=guest_client):
self.clustertasks.create_cluster(Mock(), self.cluster_id)
mock_update_status.assert_called_with('1232')
mock_reset_task.assert_called_with()
@patch.object(ClusterTasks, 'update_statuses_on_failure')
@patch('trove.common.strategies.cluster.experimental.pxc.taskmanager.LOG')
@patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
@patch('trove.common.strategies.cluster.experimental.galera_common.'
'taskmanager.LOG')
def test_grow_cluster_does_not_exist(self, mock_logging,
mock_update_status):
context = Mock()
@ -169,12 +168,13 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
'1234',
status=InstanceTasks.GROWING_ERROR)
@patch.object(ClusterTasks, '_check_cluster_for_root')
@patch.object(ClusterTasks, 'reset_task')
@patch.object(ClusterTasks, '_render_cluster_config')
@patch.object(ClusterTasks, 'get_ip')
@patch.object(ClusterTasks, 'get_guest')
@patch.object(ClusterTasks, '_all_instances_ready', return_value=True)
@patch.object(GaleraCommonClusterTasks, '_check_cluster_for_root')
@patch.object(GaleraCommonClusterTasks, 'reset_task')
@patch.object(GaleraCommonClusterTasks, '_render_cluster_config')
@patch.object(GaleraCommonClusterTasks, 'get_ip')
@patch.object(GaleraCommonClusterTasks, 'get_guest')
@patch.object(GaleraCommonClusterTasks, '_all_instances_ready',
return_value=True)
@patch.object(Instance, 'load')
@patch.object(DBInstance, 'find_all')
@patch.object(datastore_models.Datastore, 'load')
@ -195,13 +195,13 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
new_instances)
mock_reset_task.assert_called_with()
@patch.object(ClusterTasks, 'reset_task')
@patch.object(GaleraCommonClusterTasks, 'reset_task')
@patch.object(Instance, 'load')
@patch.object(Instance, 'delete')
@patch.object(DBInstance, 'find_all')
@patch.object(ClusterTasks, 'get_guest')
@patch.object(ClusterTasks, 'get_ip')
@patch.object(ClusterTasks, '_render_cluster_config')
@patch.object(GaleraCommonClusterTasks, 'get_guest')
@patch.object(GaleraCommonClusterTasks, 'get_ip')
@patch.object(GaleraCommonClusterTasks, '_render_cluster_config')
def test_shrink_cluster_success(self, mock_render, mock_ip, mock_guest,
mock_find_all, mock_delete, mock_load,
mock_reset_task):
@ -215,8 +215,9 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
remove_instances)
mock_reset_task.assert_called_with()
@patch.object(ClusterTasks, 'update_statuses_on_failure')
@patch('trove.common.strategies.cluster.experimental.pxc.taskmanager.LOG')
@patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
@patch('trove.common.strategies.cluster.experimental.galera_common.'
'taskmanager.LOG')
def test_shrink_cluster_does_not_exist(self, mock_logging,
mock_update_status):
context = Mock()
@ -229,17 +230,17 @@ class PXCClusterTasksTest(trove_testtools.TestCase):
status=InstanceTasks.SHRINKING_ERROR)
class PXCTaskManagerStrategyTest(trove_testtools.TestCase):
class GaleraTaskManagerStrategyTest(trove_testtools.TestCase):
def test_task_manager_cluster_tasks_class(self):
percona_strategy = task_strategy()
strategy = GaleraCommonTaskManagerStrategy()
self.assertFalse(
hasattr(percona_strategy.task_manager_cluster_tasks_class,
hasattr(strategy.task_manager_cluster_tasks_class,
'rebuild_cluster'))
self.assertTrue(callable(
percona_strategy.task_manager_cluster_tasks_class.create_cluster))
strategy.task_manager_cluster_tasks_class.create_cluster))
def test_task_manager_api_class(self):
percona_strategy = task_strategy()
self.assertFalse(hasattr(percona_strategy.task_manager_api_class,
strategy = GaleraCommonTaskManagerStrategy()
self.assertFalse(hasattr(strategy.task_manager_api_class,
'add_new_node'))