diff --git a/trove/common/cfg.py b/trove/common/cfg.py index 3e23edab3f..be51d50173 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -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', @@ -1183,7 +1184,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).'), @@ -1251,6 +1252,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 diff --git a/trove/common/strategies/cluster/experimental/pxc/__init__.py b/trove/common/strategies/cluster/experimental/galera_common/__init__.py similarity index 100% rename from trove/common/strategies/cluster/experimental/pxc/__init__.py rename to trove/common/strategies/cluster/experimental/galera_common/__init__.py diff --git a/trove/common/strategies/cluster/experimental/pxc/api.py b/trove/common/strategies/cluster/experimental/galera_common/api.py similarity index 76% rename from trove/common/strategies/cluster/experimental/pxc/api.py rename to trove/common/strategies/cluster/experimental/galera_common/api.py index 79c3caca4b..58f833e26f 100644 --- a/trove/common/strategies/cluster/experimental/pxc/api.py +++ b/trove/common/strategies/cluster/experimental/galera_common/api.py @@ -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']) diff --git a/trove/common/strategies/cluster/experimental/pxc/guestagent.py b/trove/common/strategies/cluster/experimental/galera_common/guestagent.py similarity index 88% rename from trove/common/strategies/cluster/experimental/pxc/guestagent.py rename to trove/common/strategies/cluster/experimental/galera_common/guestagent.py index 425cd9001b..7510e29a8b 100644 --- a/trove/common/strategies/cluster/experimental/pxc/guestagent.py +++ b/trove/common/strategies/cluster/experimental/galera_common/guestagent.py @@ -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, diff --git a/trove/common/strategies/cluster/experimental/pxc/taskmanager.py b/trove/common/strategies/cluster/experimental/galera_common/taskmanager.py similarity index 95% rename from trove/common/strategies/cluster/experimental/pxc/taskmanager.py rename to trove/common/strategies/cluster/experimental/galera_common/taskmanager.py index 615747f009..7374538d4d 100644 --- a/trove/common/strategies/cluster/experimental/pxc/taskmanager.py +++ b/trove/common/strategies/cluster/experimental/galera_common/taskmanager.py @@ -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) diff --git a/trove/guestagent/datastore/experimental/mariadb/manager.py b/trove/guestagent/datastore/experimental/mariadb/manager.py index 8efb8f7508..9f0f7534a9 100644 --- a/trove/guestagent/datastore/experimental/mariadb/manager.py +++ b/trove/guestagent/datastore/experimental/mariadb/manager.py @@ -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) diff --git a/trove/guestagent/datastore/experimental/mariadb/service.py b/trove/guestagent/datastore/experimental/mariadb/service.py index 758be7c384..e7dddb38c5 100644 --- a/trove/guestagent/datastore/experimental/mariadb/service.py +++ b/trove/guestagent/datastore/experimental/mariadb/service.py @@ -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) diff --git a/trove/guestagent/datastore/experimental/pxc/manager.py b/trove/guestagent/datastore/experimental/pxc/manager.py index de4beee80c..caf3fc3aad 100644 --- a/trove/guestagent/datastore/experimental/pxc/manager.py +++ b/trove/guestagent/datastore/experimental/pxc/manager.py @@ -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) diff --git a/trove/guestagent/datastore/experimental/pxc/service.py b/trove/guestagent/datastore/experimental/pxc/service.py index a61b0a7863..8c541c5277 100644 --- a/trove/guestagent/datastore/experimental/pxc/service.py +++ b/trove/guestagent/datastore/experimental/pxc/service.py @@ -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) diff --git a/trove/guestagent/datastore/experimental/pxc/system.py b/trove/guestagent/datastore/experimental/pxc/system.py deleted file mode 100644 index 8a945959da..0000000000 --- a/trove/guestagent/datastore/experimental/pxc/system.py +++ /dev/null @@ -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 diff --git a/trove/guestagent/datastore/galera_common/__init__.py b/trove/guestagent/datastore/galera_common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/trove/guestagent/datastore/galera_common/manager.py b/trove/guestagent/datastore/galera_common/manager.py new file mode 100644 index 0000000000..a69756ec0c --- /dev/null +++ b/trove/guestagent/datastore/galera_common/manager.py @@ -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) diff --git a/trove/guestagent/datastore/galera_common/service.py b/trove/guestagent/datastore/galera_common/service.py new file mode 100644 index 0000000000..e286609372 --- /dev/null +++ b/trove/guestagent/datastore/galera_common/service.py @@ -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() + } diff --git a/trove/guestagent/datastore/mysql_common/service.py b/trove/guestagent/datastore/mysql_common/service.py index 5f98a61459..90a83ee26c 100644 --- a/trove/guestagent/datastore/mysql_common/service.py +++ b/trove/guestagent/datastore/mysql_common/service.py @@ -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: diff --git a/trove/templates/mariadb/cluster.config.template b/trove/templates/mariadb/cluster.config.template new file mode 100644 index 0000000000..06f124a6fc --- /dev/null +++ b/trove/templates/mariadb/cluster.config.template @@ -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 }} diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py index a49a48043c..eb2255908e 100644 --- a/trove/tests/int_tests.py +++ b/trove/tests/int_tests.py @@ -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) diff --git a/trove/tests/scenario/helpers/cassandra_helper.py b/trove/tests/scenario/helpers/cassandra_helper.py index 7aad13a6c4..385b08c60f 100644 --- a/trove/tests/scenario/helpers/cassandra_helper.py +++ b/trove/tests/scenario/helpers/cassandra_helper.py @@ -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) diff --git a/trove/tests/scenario/helpers/mariadb_helper.py b/trove/tests/scenario/helpers/mariadb_helper.py index 687b87631a..19986e9a68 100644 --- a/trove/tests/scenario/helpers/mariadb_helper.py +++ b/trove/tests/scenario/helpers/mariadb_helper.py @@ -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) diff --git a/trove/tests/scenario/helpers/mongodb_helper.py b/trove/tests/scenario/helpers/mongodb_helper.py index 0fbb7de74b..7710ab6c41 100644 --- a/trove/tests/scenario/helpers/mongodb_helper.py +++ b/trove/tests/scenario/helpers/mongodb_helper.py @@ -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) diff --git a/trove/tests/scenario/helpers/pxc_helper.py b/trove/tests/scenario/helpers/pxc_helper.py index 7bd58c358c..c26276d55c 100644 --- a/trove/tests/scenario/helpers/pxc_helper.py +++ b/trove/tests/scenario/helpers/pxc_helper.py @@ -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) diff --git a/trove/tests/scenario/helpers/redis_helper.py b/trove/tests/scenario/helpers/redis_helper.py index e07768ec36..80b5358d29 100644 --- a/trove/tests/scenario/helpers/redis_helper.py +++ b/trove/tests/scenario/helpers/redis_helper.py @@ -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) diff --git a/trove/tests/scenario/runners/cluster_actions_runners.py b/trove/tests/scenario/runners/cluster_actions_runners.py index be339bc3a1..7d692b9b1c 100644 --- a/trove/tests/scenario/runners/cluster_actions_runners.py +++ b/trove/tests/scenario/runners/cluster_actions_runners.py @@ -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.") diff --git a/trove/tests/unittests/cluster/test_pxc_cluster.py b/trove/tests/unittests/cluster/test_galera_cluster.py similarity index 89% rename from trove/tests/unittests/cluster/test_pxc_cluster.py rename to trove/tests/unittests/cluster/test_galera_cluster.py index cf0d5bf249..960a6c01bc 100644 --- a/trove/tests/unittests/cluster/test_pxc_cluster.py +++ b/trove/tests/unittests/cluster/test_galera_cluster.py @@ -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') diff --git a/trove/tests/unittests/guestagent/test_dbaas.py b/trove/tests/unittests/guestagent/test_dbaas.py index 02a5759780..e807800ebd 100644 --- a/trove/tests/unittests/guestagent/test_dbaas.py +++ b/trove/tests/unittests/guestagent/test_dbaas.py @@ -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): diff --git a/trove/tests/unittests/guestagent/test_pxc_api.py b/trove/tests/unittests/guestagent/test_galera_cluster_api.py similarity index 93% rename from trove/tests/unittests/guestagent/test_pxc_api.py rename to trove/tests/unittests/guestagent/test_galera_cluster_api.py index ed03086411..9f79eb5664 100644 --- a/trove/tests/unittests/guestagent/test_pxc_api.py +++ b/trove/tests/unittests/guestagent/test_galera_cluster_api.py @@ -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): diff --git a/trove/tests/unittests/guestagent/test_galera_manager.py b/trove/tests/unittests/guestagent/test_galera_manager.py new file mode 100644 index 0000000000..487ba7d975 --- /dev/null +++ b/trove/tests/unittests/guestagent/test_galera_manager.py @@ -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) diff --git a/trove/tests/unittests/guestagent/test_mariadb_manager.py b/trove/tests/unittests/guestagent/test_mariadb_manager.py new file mode 100644 index 0000000000..cd889afbc5 --- /dev/null +++ b/trove/tests/unittests/guestagent/test_mariadb_manager.py @@ -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) diff --git a/trove/tests/unittests/guestagent/test_pxc_manager.py b/trove/tests/unittests/guestagent/test_pxc_manager.py deleted file mode 100644 index 9c86833742..0000000000 --- a/trove/tests/unittests/guestagent/test_pxc_manager.py +++ /dev/null @@ -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) diff --git a/trove/tests/unittests/taskmanager/test_pxc_clusters.py b/trove/tests/unittests/taskmanager/test_galera_clusters.py similarity index 78% rename from trove/tests/unittests/taskmanager/test_pxc_clusters.py rename to trove/tests/unittests/taskmanager/test_galera_clusters.py index 6fda2f5f1c..520bc54b7c 100644 --- a/trove/tests/unittests/taskmanager/test_pxc_clusters.py +++ b/trove/tests/unittests/taskmanager/test_galera_clusters.py @@ -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'))