summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-02-05 06:46:47 +0000
committerGerrit Code Review <review@openstack.org>2017-02-05 06:46:47 +0000
commitb5cf0c58f24e8196a8f358f156df8aa8c094df6d (patch)
tree4c2d6b494fb8f2fa9efa4a5dce183935c02cae42
parent577d43b02d200b5b60eefc875f1542faa09ebfd9 (diff)
parent9bca402ec3f651fb9017c20dc27601a548cc79e1 (diff)
downloadtrove-b5cf0c58f24e8196a8f358f156df8aa8c094df6d.tar.gz
Merge "Add configuration support for clusters"
-rw-r--r--releasenotes/notes/cluster-configuration-groups-37f7de9e5a343165.yaml3
-rw-r--r--tools/trove-pylint.config40
-rw-r--r--trove/cluster/models.py173
-rw-r--r--trove/cluster/service.py4
-rw-r--r--trove/cluster/tasks.py2
-rw-r--r--trove/cluster/views.py2
-rw-r--r--trove/common/exception.py5
-rw-r--r--trove/common/notification.py22
-rw-r--r--trove/common/strategies/cluster/experimental/cassandra/api.py21
-rw-r--r--trove/common/strategies/cluster/experimental/galera_common/api.py22
-rw-r--r--trove/common/strategies/cluster/experimental/mongodb/api.py5
-rw-r--r--trove/common/strategies/cluster/experimental/redis/api.py5
-rw-r--r--trove/common/strategies/cluster/experimental/vertica/api.py5
-rw-r--r--trove/configuration/models.py16
-rw-r--r--trove/configuration/service.py39
-rw-r--r--trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py38
-rw-r--r--trove/instance/models.py172
-rw-r--r--trove/instance/service.py4
-rw-r--r--trove/taskmanager/manager.py9
-rw-r--r--trove/tests/int_tests.py11
-rw-r--r--trove/tests/scenario/groups/__init__.py3
-rw-r--r--trove/tests/scenario/groups/cluster_group.py143
-rw-r--r--trove/tests/scenario/runners/cluster_runners.py238
-rw-r--r--trove/tests/scenario/runners/instance_create_runners.py20
-rw-r--r--trove/tests/scenario/runners/test_runners.py20
-rw-r--r--trove/tests/unittests/cluster/test_cassandra_cluster.py2
-rw-r--r--trove/tests/unittests/cluster/test_cluster.py12
-rw-r--r--trove/tests/unittests/cluster/test_cluster_controller.py2
-rw-r--r--trove/tests/unittests/cluster/test_cluster_pxc_controller.py2
-rw-r--r--trove/tests/unittests/cluster/test_cluster_redis_controller.py2
-rw-r--r--trove/tests/unittests/cluster/test_cluster_vertica_controller.py2
-rw-r--r--trove/tests/unittests/cluster/test_galera_cluster.py27
-rw-r--r--trove/tests/unittests/cluster/test_redis_cluster.py12
-rw-r--r--trove/tests/unittests/cluster/test_vertica_cluster.py24
-rw-r--r--trove/tests/unittests/instance/test_instance_controller.py14
35 files changed, 909 insertions, 212 deletions
diff --git a/releasenotes/notes/cluster-configuration-groups-37f7de9e5a343165.yaml b/releasenotes/notes/cluster-configuration-groups-37f7de9e5a343165.yaml
new file mode 100644
index 00000000..521685e4
--- /dev/null
+++ b/releasenotes/notes/cluster-configuration-groups-37f7de9e5a343165.yaml
@@ -0,0 +1,3 @@
+features:
+ - Support attaching and detaching of configuration
+ groups on clusters.
diff --git a/tools/trove-pylint.config b/tools/trove-pylint.config
index 6a38a8be..01ced46f 100644
--- a/tools/trove-pylint.config
+++ b/tools/trove-pylint.config
@@ -492,13 +492,13 @@
[
"trove/configuration/service.py",
"E1101",
- "Instance of 'BuiltInstance' has no 'update_overrides' member",
+ "Instance of 'BuiltInstance' has no 'update_configuration' member",
"ConfigurationsController._refresh_on_all_instances"
],
[
"trove/configuration/service.py",
"no-member",
- "Instance of 'BuiltInstance' has no 'update_overrides' member",
+ "Instance of 'BuiltInstance' has no 'update_configuration' member",
"ConfigurationsController._refresh_on_all_instances"
],
[
@@ -754,6 +754,18 @@
"upgrade"
],
[
+ "trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py",
+ "E1101",
+ "Instance of 'Table' has no 'create_column' member",
+ "upgrade"
+ ],
+ [
+ "trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py",
+ "no-member",
+ "Instance of 'Table' has no 'create_column' member",
+ "upgrade"
+ ],
+ [
"trove/db/sqlalchemy/migration.py",
"E0611",
"No name 'exceptions' in module 'migrate.versioning'",
@@ -1302,18 +1314,6 @@
[
"trove/taskmanager/manager.py",
"E1101",
- "Instance of 'BuiltInstance' has no 'unassign_configuration' member",
- "Manager.unassign_configuration"
- ],
- [
- "trove/taskmanager/manager.py",
- "E1101",
- "Instance of 'BuiltInstance' has no 'update_overrides' member",
- "Manager.update_overrides"
- ],
- [
- "trove/taskmanager/manager.py",
- "E1101",
"Instance of 'BuiltInstance' has no 'upgrade' member",
"Manager.upgrade"
],
@@ -1386,18 +1386,6 @@
[
"trove/taskmanager/manager.py",
"no-member",
- "Instance of 'BuiltInstance' has no 'unassign_configuration' member",
- "Manager.unassign_configuration"
- ],
- [
- "trove/taskmanager/manager.py",
- "no-member",
- "Instance of 'BuiltInstance' has no 'update_overrides' member",
- "Manager.update_overrides"
- ],
- [
- "trove/taskmanager/manager.py",
- "no-member",
"Instance of 'BuiltInstance' has no 'upgrade' member",
"Manager.upgrade"
],
diff --git a/trove/cluster/models.py b/trove/cluster/models.py
index 69a80f06..3742e349 100644
--- a/trove/cluster/models.py
+++ b/trove/cluster/models.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+
from oslo_log import log as logging
from novaclient import exceptions as nova_exceptions
@@ -21,18 +23,27 @@ from trove.cluster.tasks import ClusterTasks
from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
-from trove.common.notification import (DBaaSClusterGrow, DBaaSClusterShrink,
- DBaaSClusterResetStatus,
- DBaaSClusterRestart)
+from trove.common.notification import (
+ DBaaSClusterAttachConfiguration,
+ DBaaSClusterDetachConfiguration,
+ DBaaSClusterGrow,
+ DBaaSClusterShrink,
+ DBaaSClusterResetStatus,
+ DBaaSClusterRestart)
from trove.common.notification import DBaaSClusterUpgrade
+from trove.common.notification import DBaaSInstanceAttachConfiguration
+from trove.common.notification import DBaaSInstanceDetachConfiguration
+from trove.common.notification import EndNotification
from trove.common.notification import StartNotification
from trove.common import remote
from trove.common import server_group as srv_grp
from trove.common.strategies.cluster import strategy
from trove.common import utils
+from trove.configuration import models as config_models
from trove.datastore import models as datastore_models
from trove.db import models as dbmodels
from trove.instance import models as inst_models
+from trove.instance.tasks import InstanceTasks
from trove.taskmanager import api as task_api
@@ -49,7 +60,7 @@ def persisted_models():
class DBCluster(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'created', 'updated', 'name', 'task_id',
'tenant_id', 'datastore_version_id', 'deleted',
- 'deleted_at']
+ 'deleted_at', 'configuration_id']
def __init__(self, task_status, **kwargs):
"""
@@ -140,7 +151,6 @@ class Cluster(object):
self.update_db(task_status=ClusterTasks.NONE)
def reset_status(self):
- self.validate_cluster_available([ClusterTasks.BUILDING_INITIAL])
LOG.info(_("Resetting status to NONE on cluster %s") % self.id)
self.reset_task()
instances = inst_models.DBInstance.find_all(cluster_id=self.id,
@@ -198,6 +208,10 @@ class Cluster(object):
return self.db_info.deleted_at
@property
+ def configuration_id(self):
+ return self.db_info.configuration_id
+
+ @property
def db_instances(self):
"""DBInstance objects are persistent, therefore cacheable."""
if not self._db_instances:
@@ -246,14 +260,14 @@ class Cluster(object):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
locality = srv_grp.ServerGroup.build_scheduler_hint(
context, locality, name)
api_strategy = strategy.load_api_strategy(datastore_version.manager)
return api_strategy.cluster_class.create(context, name, datastore,
datastore_version, instances,
extended_properties,
- locality)
+ locality, configuration)
def validate_cluster_available(self, valid_states=[ClusterTasks.NONE]):
if self.db_info.task_status not in valid_states:
@@ -304,6 +318,11 @@ class Cluster(object):
if 'availability_zone' in node:
instance['availability_zone'] = (
node['availability_zone'])
+ if 'type' in node:
+ instance_type = node['type']
+ if isinstance(instance_type, six.string_types):
+ instance_type = instance_type.split(',')
+ instance['instance_type'] = instance_type
instances.append(instance)
return self.grow(instances)
elif action == 'shrink':
@@ -328,7 +347,23 @@ class Cluster(object):
dv = datastore_models.DatastoreVersion.load(self.datastore, dv_id)
with StartNotification(context, cluster_id=self.id,
datastore_version=dv.id):
- return self.upgrade(dv)
+ self.upgrade(dv)
+ self.update_db(datastore_version_id=dv.id)
+
+ elif action == 'configuration_attach':
+ configuration_id = param['configuration_id']
+ context.notification = DBaaSClusterAttachConfiguration(context,
+ request=req)
+ with StartNotification(context, cluster_id=self.id,
+ configuration_id=configuration_id):
+ return self.configuration_attach(configuration_id)
+
+ elif action == 'configuration_detach':
+ context.notification = DBaaSClusterDetachConfiguration(context,
+ request=req)
+ with StartNotification(context, cluster_id=self.id):
+ return self.configuration_detach()
+
else:
raise exception.BadRequest(_("Action %s not supported") % action)
@@ -376,6 +411,128 @@ class Cluster(object):
def upgrade(self, datastore_version):
raise exception.BadRequest(_("Action 'upgrade' not supported"))
+ def configuration_attach(self, configuration_id):
+ raise exception.BadRequest(
+ _("Action 'configuration_attach' not supported"))
+
+ def rolling_configuration_update(self, configuration_id,
+ apply_on_all=True):
+ cluster_notification = self.context.notification
+ request_info = cluster_notification.serialize(self.context)
+ self.validate_cluster_available()
+ self.db_info.update(task_status=ClusterTasks.UPDATING_CLUSTER)
+ try:
+ configuration = config_models.Configuration.find(
+ self.context, configuration_id, self.datastore_version.id)
+ instances = [inst_models.Instance.load(self.context, instance.id)
+ for instance in self.instances]
+
+ LOG.debug("Persisting changes on cluster nodes.")
+ # Allow re-applying the same configuration (e.g. on configuration
+ # updates).
+ for instance in instances:
+ if not (instance.configuration and
+ instance.configuration.id != configuration_id):
+ self.context.notification = (
+ DBaaSInstanceAttachConfiguration(self.context,
+ **request_info))
+ with StartNotification(self.context,
+ instance_id=instance.id,
+ configuration_id=configuration_id):
+ with EndNotification(self.context):
+ instance.save_configuration(configuration)
+ else:
+ LOG.debug(
+ "Node '%s' already has the configuration '%s' "
+ "attached." % (instance.id, configuration_id))
+
+ # Configuration has been persisted to all instances.
+ # The cluster is in a consistent state with all nodes
+ # requiring restart.
+ # We therefore assign the configuration group ID now.
+ # The configuration can be safely detached at this point.
+ self.update_db(configuration_id=configuration_id)
+
+ LOG.debug("Applying runtime configuration changes.")
+ if instances[0].apply_configuration(configuration):
+ LOG.debug(
+ "Runtime changes have been applied successfully to the "
+ "first node.")
+ remaining_nodes = instances[1:]
+ if apply_on_all:
+ LOG.debug(
+ "Applying the changes to the remaining nodes.")
+ for instance in remaining_nodes:
+ instance.apply_configuration(configuration)
+ else:
+ LOG.debug(
+ "Releasing restart-required task on the remaining "
+ "nodes.")
+ for instance in remaining_nodes:
+ instance.update_db(task_status=InstanceTasks.NONE)
+ finally:
+ self.update_db(task_status=ClusterTasks.NONE)
+
+ return self.__class__(self.context, self.db_info,
+ self.ds, self.ds_version)
+
+ def configuration_detach(self):
+ raise exception.BadRequest(
+ _("Action 'configuration_detach' not supported"))
+
+ def rolling_configuration_remove(self, apply_on_all=True):
+ cluster_notification = self.context.notification
+ request_info = cluster_notification.serialize(self.context)
+ self.validate_cluster_available()
+ self.db_info.update(task_status=ClusterTasks.UPDATING_CLUSTER)
+ try:
+ instances = [inst_models.Instance.load(self.context, instance.id)
+ for instance in self.instances]
+
+ LOG.debug("Removing changes from cluster nodes.")
+ for instance in instances:
+ if instance.configuration:
+ self.context.notification = (
+ DBaaSInstanceDetachConfiguration(self.context,
+ **request_info))
+ with StartNotification(self.context,
+ instance_id=instance.id):
+ with EndNotification(self.context):
+ instance.delete_configuration()
+ else:
+ LOG.debug(
+ "Node '%s' has no configuration attached."
+ % instance.id)
+
+ # The cluster is in a consistent state with all nodes
+ # requiring restart.
+ # New configuration can be safely attached at this point.
+ configuration_id = self.configuration_id
+ self.update_db(configuration_id=None)
+
+ LOG.debug("Applying runtime configuration changes.")
+ if instances[0].reset_configuration(configuration_id):
+ LOG.debug(
+ "Runtime changes have been applied successfully to the "
+ "first node.")
+ remaining_nodes = instances[1:]
+ if apply_on_all:
+ LOG.debug(
+ "Applying the changes to the remaining nodes.")
+ for instance in remaining_nodes:
+ instance.reset_configuration(configuration_id)
+ else:
+ LOG.debug(
+ "Releasing restart-required task on the remaining "
+ "nodes.")
+ for instance in remaining_nodes:
+ instance.update_db(task_status=InstanceTasks.NONE)
+ finally:
+ self.update_db(task_status=ClusterTasks.NONE)
+
+ return self.__class__(self.context, self.db_info,
+ self.ds, self.ds_version)
+
@staticmethod
def load_instance(context, cluster_id, instance_id):
return inst_models.load_instance_with_info(
diff --git a/trove/cluster/service.py b/trove/cluster/service.py
index 67e7a24a..00ec3284 100644
--- a/trove/cluster/service.py
+++ b/trove/cluster/service.py
@@ -218,6 +218,8 @@ class ClusterController(wsgi.Controller):
if locality not in locality_domain:
raise exception.BadRequest(msg=locality_domain_msg)
+ configuration = body['cluster'].get('configuration')
+
context.notification = notification.DBaaSClusterCreate(context,
request=req)
with StartNotification(context, name=name, datastore=datastore.name,
@@ -225,7 +227,7 @@ class ClusterController(wsgi.Controller):
cluster = models.Cluster.create(context, name, datastore,
datastore_version, instances,
extended_properties,
- locality)
+ locality, configuration)
cluster.locality = locality
view = views.load_view(cluster, req=req, load_servers=False)
return wsgi.Result(view.data(), 200)
diff --git a/trove/cluster/tasks.py b/trove/cluster/tasks.py
index 44d6d301..e7d54c51 100644
--- a/trove/cluster/tasks.py
+++ b/trove/cluster/tasks.py
@@ -73,6 +73,8 @@ class ClusterTasks(object):
0x07, 'UPGRADING_CLUSTER', 'Upgrading the cluster to new version.')
RESTARTING_CLUSTER = ClusterTask(
0x08, 'RESTARTING_CLUSTER', 'Restarting the cluster.')
+ UPDATING_CLUSTER = ClusterTask(
+ 0x09, 'UPDATING_CLUSTER', 'Updating cluster configuration.')
# Dissuade further additions at run-time.
diff --git a/trove/cluster/views.py b/trove/cluster/views.py
index f60ac0b7..ce669ade 100644
--- a/trove/cluster/views.py
+++ b/trove/cluster/views.py
@@ -55,6 +55,8 @@ class ClusterView(object):
if self.cluster.locality:
cluster_dict['locality'] = self.cluster.locality
+ if self.cluster.configuration_id:
+ cluster_dict['configuration'] = self.cluster.configuration_id
LOG.debug(cluster_dict)
return {"cluster": cluster_dict}
diff --git a/trove/common/exception.py b/trove/common/exception.py
index 020a9b48..3c0dd228 100644
--- a/trove/common/exception.py
+++ b/trove/common/exception.py
@@ -236,6 +236,11 @@ class UnprocessableEntity(TroveError):
message = _("Unable to process the contained request.")
+class ConfigurationNotSupported(UnprocessableEntity):
+
+ message = _("Configuration groups not supported by the datastore.")
+
+
class CannotResizeToSameSize(TroveError):
message = _("No change was requested in the size of the instance.")
diff --git a/trove/common/notification.py b/trove/common/notification.py
index e4e6d4e7..fa25b4c6 100644
--- a/trove/common/notification.py
+++ b/trove/common/notification.py
@@ -549,6 +549,28 @@ class DBaaSInstanceDetachConfiguration(DBaaSAPINotification):
return ['instance_id']
+class DBaaSClusterAttachConfiguration(DBaaSAPINotification):
+
+ @abc.abstractmethod
+ def event_type(self):
+ return 'cluster_attach_configuration'
+
+ @abc.abstractmethod
+ def required_start_traits(self):
+ return ['cluster_id', 'configuration_id']
+
+
+class DBaaSClusterDetachConfiguration(DBaaSAPINotification):
+
+ @abc.abstractmethod
+ def event_type(self):
+ return 'cluster_detach_configuration'
+
+ @abc.abstractmethod
+ def required_start_traits(self):
+ return ['cluster_id']
+
+
class DBaaSClusterCreate(DBaaSAPINotification):
@abc.abstractmethod
diff --git a/trove/common/strategies/cluster/experimental/cassandra/api.py b/trove/common/strategies/cluster/experimental/cassandra/api.py
index ab680b34..084b716b 100644
--- a/trove/common/strategies/cluster/experimental/cassandra/api.py
+++ b/trove/common/strategies/cluster/experimental/cassandra/api.py
@@ -82,19 +82,20 @@ class CassandraCluster(models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
LOG.debug("Processing a request for creating a new cluster.")
# Updating Cluster Task.
db_info = models.DBCluster.create(
name=name, tenant_id=context.tenant,
datastore_version_id=datastore_version.id,
- task_status=ClusterTasks.BUILDING_INITIAL)
+ task_status=ClusterTasks.BUILDING_INITIAL,
+ configuration_id=configuration)
cls._create_cluster_instances(
context, db_info.id, db_info.name,
datastore, datastore_version, instances, extended_properties,
- locality)
+ locality, configuration)
# Calling taskmanager to further proceed for cluster-configuration.
task_api.load(context, datastore_version.manager).create_cluster(
@@ -106,7 +107,7 @@ class CassandraCluster(models.Cluster):
def _create_cluster_instances(
cls, context, cluster_id, cluster_name,
datastore, datastore_version, instances, extended_properties,
- locality):
+ locality, configuration_id):
LOG.debug("Processing a request for new cluster instances.")
cassandra_conf = CONF.get(datastore_version.manager)
@@ -153,7 +154,7 @@ class CassandraCluster(models.Cluster):
instance['volume_size'], None,
nics=instance.get('nics', None),
availability_zone=instance_az,
- configuration_id=None,
+ configuration_id=configuration_id,
cluster_config=member_config,
modules=instance.get('modules'),
locality=locality,
@@ -180,9 +181,11 @@ class CassandraCluster(models.Cluster):
db_info.update(task_status=ClusterTasks.GROWING_CLUSTER)
locality = srv_grp.ServerGroup.convert_to_hint(self.server_group)
+ configuration_id = self.db_info.configuration_id
+
new_instances = self._create_cluster_instances(
context, db_info.id, db_info.name, datastore, datastore_version,
- instances, None, locality)
+ instances, None, locality, configuration_id)
task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances])
@@ -212,6 +215,12 @@ class CassandraCluster(models.Cluster):
def upgrade(self, datastore_version):
self.rolling_upgrade(datastore_version)
+ def configuration_attach(self, configuration_id):
+ self.rolling_configuration_update(configuration_id, apply_on_all=False)
+
+ def configuration_detach(self):
+ self.rolling_configuration_remove(apply_on_all=False)
+
class CassandraClusterView(ClusterView):
diff --git a/trove/common/strategies/cluster/experimental/galera_common/api.py b/trove/common/strategies/cluster/experimental/galera_common/api.py
index 9b47291f..5083df73 100644
--- a/trove/common/strategies/cluster/experimental/galera_common/api.py
+++ b/trove/common/strategies/cluster/experimental/galera_common/api.py
@@ -97,7 +97,8 @@ class GaleraCommonCluster(cluster_models.Cluster):
@staticmethod
def _create_instances(context, db_info, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality,
+ configuration_id):
member_config = {"id": db_info.id,
"instance_type": "member"}
name_index = 1
@@ -118,7 +119,7 @@ class GaleraCommonCluster(cluster_models.Cluster):
availability_zone=instance.get(
'availability_zone', None),
nics=instance.get('nics', None),
- configuration_id=None,
+ configuration_id=configuration_id,
cluster_config=member_config,
modules=instance.get('modules'),
locality=locality,
@@ -128,7 +129,7 @@ class GaleraCommonCluster(cluster_models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
LOG.debug("Initiating Galera cluster creation.")
cls._validate_cluster_instances(context, instances, datastore,
datastore_version)
@@ -136,10 +137,12 @@ class GaleraCommonCluster(cluster_models.Cluster):
db_info = cluster_models.DBCluster.create(
name=name, tenant_id=context.tenant,
datastore_version_id=datastore_version.id,
- task_status=ClusterTasks.BUILDING_INITIAL)
+ task_status=ClusterTasks.BUILDING_INITIAL,
+ configuration_id=configuration)
cls._create_instances(context, db_info, datastore, datastore_version,
- instances, extended_properties, locality)
+ instances, extended_properties, locality,
+ configuration)
# Calling taskmanager to further proceed for cluster-configuration
task_api.load(context, datastore_version.manager).create_cluster(
@@ -160,9 +163,10 @@ class GaleraCommonCluster(cluster_models.Cluster):
db_info.update(task_status=ClusterTasks.GROWING_CLUSTER)
try:
locality = srv_grp.ServerGroup.convert_to_hint(self.server_group)
+ configuration_id = self.db_info.configuration_id
new_instances = self._create_instances(
context, db_info, datastore, datastore_version, instances,
- None, locality)
+ None, locality, configuration_id)
task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances])
@@ -203,6 +207,12 @@ class GaleraCommonCluster(cluster_models.Cluster):
def upgrade(self, datastore_version):
self.rolling_upgrade(datastore_version)
+ def configuration_attach(self, configuration_id):
+ self.rolling_configuration_update(configuration_id)
+
+ def configuration_detach(self):
+ self.rolling_configuration_remove()
+
class GaleraCommonClusterView(ClusterView):
diff --git a/trove/common/strategies/cluster/experimental/mongodb/api.py b/trove/common/strategies/cluster/experimental/mongodb/api.py
index 9c4ea4f7..87efaddd 100644
--- a/trove/common/strategies/cluster/experimental/mongodb/api.py
+++ b/trove/common/strategies/cluster/experimental/mongodb/api.py
@@ -58,7 +58,10 @@ class MongoDbCluster(models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
+
+ if configuration:
+ raise exception.ConfigurationNotSupported()
# TODO(amcreynolds): consider moving into CONF and even supporting
# TODO(amcreynolds): an array of values, e.g. [3, 5, 7]
diff --git a/trove/common/strategies/cluster/experimental/redis/api.py b/trove/common/strategies/cluster/experimental/redis/api.py
index 28186c3f..c9083190 100644
--- a/trove/common/strategies/cluster/experimental/redis/api.py
+++ b/trove/common/strategies/cluster/experimental/redis/api.py
@@ -97,9 +97,12 @@ class RedisCluster(models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
LOG.debug("Initiating cluster creation.")
+ if configuration:
+ raise exception.ConfigurationNotSupported()
+
# Updating Cluster Task
db_info = models.DBCluster.create(
diff --git a/trove/common/strategies/cluster/experimental/vertica/api.py b/trove/common/strategies/cluster/experimental/vertica/api.py
index a5f7de23..26e98b22 100644
--- a/trove/common/strategies/cluster/experimental/vertica/api.py
+++ b/trove/common/strategies/cluster/experimental/vertica/api.py
@@ -129,9 +129,12 @@ class VerticaCluster(models.Cluster):
@classmethod
def create(cls, context, name, datastore, datastore_version,
- instances, extended_properties, locality):
+ instances, extended_properties, locality, configuration):
LOG.debug("Initiating cluster creation.")
+ if configuration:
+ raise exception.ConfigurationNotSupported()
+
vertica_conf = CONF.get(datastore_version.manager)
num_instances = len(instances)
diff --git a/trove/configuration/models.py b/trove/configuration/models.py
index 7a240cb5..6639cc88 100644
--- a/trove/configuration/models.py
+++ b/trove/configuration/models.py
@@ -209,6 +209,21 @@ class Configuration(object):
item["deleted_at"] = None
DBConfigurationParameter.save(item)
+ @staticmethod
+ def find(context, configuration_id, datastore_version_id):
+ try:
+ info = Configuration.load(context, configuration_id)
+ if (info.datastore_version_id == datastore_version_id):
+ return Configuration(context, configuration_id)
+ except exception.ModelNotFoundError:
+ raise exception.NotFound(
+ message='Configuration group id: %s could not be found.'
+ % configuration_id)
+
+ raise exception.ConfigurationDatastoreNotMatchInstance(
+ config_datastore_version=info.datastore_version_id,
+ instance_datastore_version=datastore_version_id)
+
class DBConfiguration(dbmodels.DatabaseModelBase):
_data_fields = ['name', 'description', 'tenant_id', 'datastore_version_id',
@@ -256,6 +271,7 @@ class DBDatastoreConfigurationParameters(dbmodels.DatabaseModelBase):
class DatastoreConfigurationParameters(object):
+
def __init__(self, db_info):
self.db_info = db_info
diff --git a/trove/configuration/service.py b/trove/configuration/service.py
index 485e6e17..e7668a88 100644
--- a/trove/configuration/service.py
+++ b/trove/configuration/service.py
@@ -18,6 +18,7 @@ from datetime import datetime
from oslo_log import log as logging
import six
+from trove.cluster import models as cluster_models
import trove.common.apischema as apischema
from trove.common import cfg
from trove.common import exception
@@ -198,6 +199,8 @@ class ConfigurationsController(wsgi.Controller):
deleted_at)
models.Configuration.save(group, items)
self._refresh_on_all_instances(context, id)
+ self._refresh_on_all_clusters(context, id)
+
return wsgi.Result(None, 202)
def edit(self, req, body, tenant_id, id):
@@ -211,25 +214,41 @@ class ConfigurationsController(wsgi.Controller):
body['configuration'])
models.Configuration.save(group, items)
self._refresh_on_all_instances(context, id)
+ self._refresh_on_all_clusters(context, id)
def _refresh_on_all_instances(self, context, configuration_id):
- """Refresh a configuration group on all its instances.
+ """Refresh a configuration group on all single instances.
"""
- dbinstances = instances_models.DBInstance.find_all(
+ LOG.debug("Re-applying configuration group '%s' to all instances."
+ % configuration_id)
+ single_instances = instances_models.DBInstance.find_all(
tenant_id=context.tenant,
configuration_id=configuration_id,
+ cluster_id=None,
deleted=False).all()
- LOG.debug(
- "All instances with configuration group '%s' on tenant '%s': %s"
- % (configuration_id, context.tenant, dbinstances))
-
config = models.Configuration(context, configuration_id)
- for dbinstance in dbinstances:
- LOG.debug("Applying configuration group '%s' to instance: %s"
- % (configuration_id, dbinstance.id))
+ for dbinstance in single_instances:
+ LOG.debug("Re-applying configuration to instance: %s"
+ % dbinstance.id)
instance = instances_models.Instance.load(context, dbinstance.id)
- instance.update_overrides(config)
+ instance.update_configuration(config)
+
+ def _refresh_on_all_clusters(self, context, configuration_id):
+ """Refresh a configuration group on all clusters.
+ """
+ LOG.debug("Re-applying configuration group '%s' to all clusters."
+ % configuration_id)
+ clusters = cluster_models.DBCluster.find_all(
+ tenant_id=context.tenant,
+ configuration_id=configuration_id,
+ deleted=False).all()
+
+ for dbcluster in clusters:
+ LOG.debug("Re-applying configuration to cluster: %s"
+ % dbcluster.id)
+ cluster = cluster_models.Cluster.load(context, dbcluster.id)
+ cluster.configuration_attach(configuration_id)
def _configuration_items_list(self, group, configuration):
ds_version_id = group.datastore_version_id
diff --git a/trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py b/trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py
new file mode 100644
index 00000000..9d4c199f
--- /dev/null
+++ b/trove/db/sqlalchemy/migrate_repo/versions/042_add_cluster_configuration_id.py
@@ -0,0 +1,38 @@
+# 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 sqlalchemy import ForeignKey
+from sqlalchemy.schema import Column
+from sqlalchemy.schema import MetaData
+
+from trove.common import cfg
+from trove.db.sqlalchemy.migrate_repo.schema import String
+from trove.db.sqlalchemy.migrate_repo.schema import Table
+
+
+CONF = cfg.CONF
+logger = logging.getLogger('trove.db.sqlalchemy.migrate_repo.schema')
+
+meta = MetaData()
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ # Load 'configurations' table to MetaData.
+ Table('configurations', meta, autoload=True, autoload_with=migrate_engine)
+ instances = Table('clusters', meta, autoload=True)
+ instances.create_column(Column('configuration_id', String(36),
+ ForeignKey("configurations.id")))
diff --git a/trove/instance/models.py b/trove/instance/models.py
index 5cc53ec5..0621c2cc 100644
--- a/trove/instance/models.py
+++ b/trove/instance/models.py
@@ -1282,11 +1282,6 @@ class Instance(BuiltInstance):
Raises exception if a configuration assign cannot
currently be performed
"""
- # check if the instance already has a configuration assigned
- if self.db_info.configuration_id:
- raise exception.ConfigurationAlreadyAttached(
- instance_id=self.id,
- configuration_id=self.db_info.configuration_id)
# check if the instance is not ACTIVE or has tasks
status = None
@@ -1299,23 +1294,118 @@ class Instance(BuiltInstance):
raise exception.InvalidInstanceState(instance_id=self.id,
status=status)
- def unassign_configuration(self):
- LOG.debug("Unassigning the configuration from the instance %s.",
- self.id)
+ def attach_configuration(self, configuration_id):
+ LOG.debug("Attaching configuration to instance: %s", self.id)
+ if not self.db_info.configuration_id:
+ self._validate_can_perform_assign()
+ LOG.debug("Attaching configuration: %s", configuration_id)
+ config = Configuration.find(self.context, configuration_id,
+ self.db_info.datastore_version_id)
+ self.update_configuration(config)
+ else:
+ raise exception.ConfigurationAlreadyAttached(
+ instance_id=self.id,
+ configuration_id=self.db_info.configuration_id)
+
+ def update_configuration(self, configuration):
+ self.save_configuration(configuration)
+ return self.apply_configuration(configuration)
+
+ def save_configuration(self, configuration):
+ """Save configuration changes on the guest.
+ Update Trove records if successful.
+ This method does not update runtime values. It sets the instance task
+ to RESTART_REQUIRED.
+ """
+
+ LOG.debug("Saving configuration on instance: %s", self.id)
+ overrides = configuration.get_configuration_overrides()
+
+ # Always put the instance into RESTART_REQUIRED state after
+ # configuration update. The sate may be released only once (and if)
+ # the configuration is successfully applied.
+ # This ensures that the instance will always be in a consistent state
+ # even if the apply never executes or fails.
+ LOG.debug("Persisting new configuration on the guest.")
+ self.guest.update_overrides(overrides)
+ LOG.debug("Configuration has been persisted on the guest.")
+
+ # Configuration has now been persisted on the instance an can be safely
+ # detached. Update our records to reflect this change irrespective of
+ # results of any further operations.
+ self.update_db(task_status=InstanceTasks.RESTART_REQUIRED,
+ configuration_id=configuration.configuration_id)
+
+ def apply_configuration(self, configuration):
+ """Apply runtime configuration changes and release the
+ RESTART_REQUIRED task.
+ Apply changes only if ALL values can be applied at once.
+ Return True if the configuration has changed.
+ """
+
+ LOG.debug("Applying configuration on instance: %s", self.id)
+ overrides = configuration.get_configuration_overrides()
+
+ if not configuration.does_configuration_need_restart():
+ LOG.debug("Applying runtime configuration changes.")
+ self.guest.apply_overrides(overrides)
+ LOG.debug("Configuration has been applied.")
+ self.update_db(task_status=InstanceTasks.NONE)
+
+ return True
+
+ LOG.debug(
+ "Configuration changes include non-dynamic settings and "
+ "will require restart to take effect.")
+
+ return False
+
+ def detach_configuration(self):
+ LOG.debug("Detaching configuration from instance: %s", self.id)
if self.configuration and self.configuration.id:
- LOG.debug("Unassigning the configuration id %s.",
- self.configuration.id)
+ self._validate_can_perform_assign()
+ LOG.debug("Detaching configuration: %s", self.configuration.id)
+ self.remove_configuration()
+ else:
+ LOG.debug("No configuration found on instance.")
+
+ def remove_configuration(self):
+ configuration_id = self.delete_configuration()
+ return self.reset_configuration(configuration_id)
+
+ def delete_configuration(self):
+ """Remove configuration changes from the guest.
+ Update Trove records if successful.
+ This method does not update runtime values. It sets the instance task
+ to RESTART_REQUIRED.
+ Return ID of the removed configuration group.
+ """
+ LOG.debug("Deleting configuration from instance: %s", self.id)
+ configuration_id = self.configuration.id
+
+ LOG.debug("Removing configuration from the guest.")
+ self.guest.update_overrides({}, remove=True)
+ LOG.debug("Configuration has been removed from the guest.")
+
+ self.update_db(task_status=InstanceTasks.RESTART_REQUIRED,
+ configuration_id=None)
- self.guest.update_overrides({}, remove=True)
+ return configuration_id
- # Dynamically reset the configuration values back to their default
- # values from the configuration template.
- # Reset the values only if the default is available for all of
- # them and restart is not required by any.
- # Mark the instance with a 'RESTART_REQUIRED' status otherwise.
+ def reset_configuration(self, configuration_id):
+ """Dynamically reset the configuration values back to their default
+ values from the configuration template and release the
+ RESTART_REQUIRED task.
+ Reset the values only if the default is available for all of
+ them and restart is not required by any.
+ Return True if the configuration has changed.
+ """
+
+ LOG.debug("Resetting configuration on instance: %s", self.id)
+ if configuration_id:
flavor = self.get_flavor()
default_config = self._render_config_dict(flavor)
- current_config = Configuration(self.context, self.configuration.id)
+ current_config = Configuration(self.context, configuration_id)
current_overrides = current_config.get_configuration_overrides()
# Check the configuration template has defaults for all modified
# values.
@@ -1323,56 +1413,22 @@ class Instance(BuiltInstance):
for key in current_overrides.keys())
if (not current_config.does_configuration_need_restart() and
has_defaults_for_all):
+ LOG.debug("Applying runtime configuration changes.")
self.guest.apply_overrides(
{k: v for k, v in default_config.items()
if k in current_overrides})
+ LOG.debug("Configuration has been applied.")
+ self.update_db(task_status=InstanceTasks.NONE)
+
+ return True
else:
LOG.debug(
"Could not revert all configuration changes dynamically. "
"A restart will be required.")
- self.update_db(task_status=InstanceTasks.RESTART_REQUIRED)
else:
- LOG.debug("No configuration found on instance. Skipping.")
-
- def assign_configuration(self, configuration_id):
- self._validate_can_perform_assign()
-
- try:
- configuration = Configuration.load(self.context, configuration_id)
- except exception.ModelNotFoundError:
- raise exception.NotFound(
- message='Configuration group id: %s could not be found.'
- % configuration_id)
-
- config_ds_v = configuration.datastore_version_id
- inst_ds_v = self.db_info.datastore_version_id
- if (config_ds_v != inst_ds_v):
- raise exception.ConfigurationDatastoreNotMatchInstance(
- config_datastore_version=config_ds_v,
- instance_datastore_version=inst_ds_v)
-
- config = Configuration(self.context, configuration.id)
- LOG.debug("Config is %s.", config)
-
- self.update_overrides(config)
- self.update_db(configuration_id=configuration.id)
+ LOG.debug("There are no values to reset.")
- def update_overrides(self, config):
- LOG.debug("Updating or removing overrides for instance %s.", self.id)
-
- overrides = config.get_configuration_overrides()
- self.guest.update_overrides(overrides)
-
- # Apply the new configuration values dynamically to the running
- # datastore service.
- # Apply overrides only if ALL values can be applied at once or mark
- # the instance with a 'RESTART_REQUIRED' status.
- if not config.does_configuration_need_restart():
- self.guest.apply_overrides(overrides)
- else:
- LOG.debug("Configuration overrides has non-dynamic settings and "
- "will require restart to take effect.")
- self.update_db(task_status=InstanceTasks.RESTART_REQUIRED)
+ return False
def _render_config_dict(self, flavor):
config = template.SingleInstanceConfigTemplate(
diff --git a/trove/instance/service.py b/trove/instance/service.py
index 031b0f89..e0cd404b 100644
--- a/trove/instance/service.py
+++ b/trove/instance/service.py
@@ -389,13 +389,13 @@ class InstanceController(wsgi.Controller):
configuration_id = kwargs['configuration_id']
with StartNotification(context, instance_id=instance.id,
configuration_id=configuration_id):
- instance.assign_configuration(configuration_id)
+ instance.attach_configuration(configuration_id)
else:
context.notification = (
notification.DBaaSInstanceDetachConfiguration(context,
request=req))
with StartNotification(context, instance_id=instance.id):
- instance.unassign_configuration()
+ instance.detach_configuration()
if 'datastore_version' in kwargs:
datastore_version = datastore_models.DatastoreVersion.load(
instance.datastore, kwargs['datastore_version'])
diff --git a/trove/taskmanager/manager.py b/trove/taskmanager/manager.py
index 8894119e..6312bb64 100644
--- a/trove/taskmanager/manager.py
+++ b/trove/taskmanager/manager.py
@@ -389,15 +389,6 @@ class Manager(periodic_task.PeriodicTasks):
with EndNotification(context):
instance_tasks.upgrade(datastore_version)
- def update_overrides(self, context, instance_id, overrides):
- instance_tasks = models.BuiltInstanceTasks.load(context, instance_id)
- instance_tasks.update_overrides(overrides)
-
- def unassign_configuration(self, context, instance_id, flavor,
- configuration_id):
- instance_tasks = models.BuiltInstanceTasks.load(context, instance_id)
- instance_tasks.unassign_configuration(flavor, configuration_id)
-
def create_cluster(self, context, cluster_id):
with EndNotification(context, cluster_id=cluster_id):
cluster_tasks = models.load_cluster_tasks(context, cluster_id)
diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py
index d1bbf43b..6bae63a1 100644
--- a/trove/tests/int_tests.py
+++ b/trove/tests/int_tests.py
@@ -168,6 +168,12 @@ cluster_restart_groups.extend([groups.CLUSTER_ACTIONS_RESTART_WAIT])
cluster_upgrade_groups = list(cluster_create_groups)
cluster_upgrade_groups.extend([groups.CLUSTER_UPGRADE_WAIT])
+cluster_config_groups = list(cluster_create_groups)
+cluster_config_groups.extend([groups.CLUSTER_CFGGRP_DELETE])
+
+cluster_config_actions_groups = list(cluster_config_groups)
+cluster_config_actions_groups.extend([groups.CLUSTER_ACTIONS_CFGGRP_ACTIONS])
+
cluster_groups = list(cluster_actions_groups)
cluster_groups.extend([cluster_group.GROUP])
@@ -254,6 +260,8 @@ register(["cluster_restart"], cluster_restart_groups)
register(["cluster_root"], cluster_root_groups)
register(["cluster_root_actions"], cluster_root_actions_groups)
register(["cluster_upgrade"], cluster_upgrade_groups)
+register(["cluster_config"], cluster_config_groups)
+register(["cluster_config_actions"], cluster_config_actions_groups)
register(["common"], common_groups)
register(["configuration"], configuration_groups)
register(["configuration_create"], configuration_create_groups)
@@ -294,7 +302,8 @@ register(
user_actions_groups, ],
multi=[cluster_actions_groups,
cluster_negative_actions_groups,
- cluster_root_actions_groups, ]
+ cluster_root_actions_groups,
+ cluster_config_actions_groups, ]
)
register(
diff --git a/trove/tests/scenario/groups/__init__.py b/trove/tests/scenario/groups/__init__.py
index 8274dd7d..e6d1a822 100644
--- a/trove/tests/scenario/groups/__init__.py
+++ b/trove/tests/scenario/groups/__init__.py
@@ -51,7 +51,10 @@ CFGGRP_INST_DELETE_WAIT = "scenario.cfggrp_inst_delete_wait_grp"
# Cluster Actions Group
+CLUSTER_CFGGRP_CREATE = "scenario.cluster_actions_cfggrp_create_grp"
+CLUSTER_CFGGRP_DELETE = "scenario.cluster_actions_cfggrp_delete_grp"
CLUSTER_ACTIONS = "scenario.cluster_actions_grp"
+CLUSTER_ACTIONS_CFGGRP_ACTIONS = "scenario.cluster_actions_cfggrp_actions_grp"
CLUSTER_ACTIONS_ROOT_ENABLE = "scenario.cluster_actions_root_enable_grp"
CLUSTER_ACTIONS_ROOT_ACTIONS = "scenario.cluster_actions_root_actions_grp"
CLUSTER_ACTIONS_ROOT_GROW = "scenario.cluster_actions_root_grow_grp"
diff --git a/trove/tests/scenario/groups/cluster_group.py b/trove/tests/scenario/groups/cluster_group.py
index 205002b9..72330cb6 100644
--- a/trove/tests/scenario/groups/cluster_group.py
+++ b/trove/tests/scenario/groups/cluster_group.py
@@ -29,7 +29,7 @@ class ClusterRunnerFactory(test_runners.RunnerFactory):
_runner_cls = 'ClusterRunner'
-@test(groups=[GROUP, groups.CLUSTER_CREATE],
+@test(groups=[GROUP, groups.CLUSTER_CFGGRP_CREATE],
runs_after_groups=[groups.MODULE_DELETE,
groups.CFGGRP_INST_DELETE,
groups.INST_ACTIONS_RESIZE_WAIT,
@@ -39,6 +39,20 @@ class ClusterRunnerFactory(test_runners.RunnerFactory):
groups.ROOT_ACTION_INST_DELETE,
groups.REPL_INST_DELETE_WAIT,
groups.INST_DELETE])
+class ClusterConfigurationCreateGroup(TestGroup):
+
+ def __init__(self):
+ super(ClusterConfigurationCreateGroup, self).__init__(
+ ClusterRunnerFactory.instance())
+
+ @test
+ def create_initial_configuration(self):
+ """Create a configuration group for a new cluster."""
+ self.test_runner.run_initial_configuration_create()
+
+
+@test(groups=[GROUP, groups.CLUSTER_ACTIONS, groups.CLUSTER_CREATE],
+ runs_after_groups=[groups.CLUSTER_CFGGRP_CREATE])
class ClusterCreateGroup(TestGroup):
def __init__(self):
@@ -71,6 +85,11 @@ class ClusterCreateWaitGroup(TestGroup):
self.test_runner.run_cluster_create_wait()
@test(depends_on=[cluster_create_wait])
+ def verify_initial_configuration(self):
+ """Verify initial configuration values on the cluster."""
+ self.test_runner.run_verify_initial_configuration()
+
+ @test(depends_on=[cluster_create_wait])
def add_initial_cluster_data(self):
"""Add data to cluster."""
self.test_runner.run_add_initial_cluster_data()
@@ -179,6 +198,11 @@ class ClusterGrowWaitGroup(TestGroup):
self.test_runner.run_cluster_grow_wait()
@test(depends_on=[cluster_grow_wait])
+ def verify_initial_configuration(self):
+ """Verify initial configuration values on the cluster."""
+ self.test_runner.run_verify_initial_configuration()
+
+ @test(depends_on=[cluster_grow_wait])
def verify_initial_cluster_data_after_grow(self):
"""Verify the initial data still exists after cluster grow."""
self.test_runner.run_verify_initial_cluster_data()
@@ -246,6 +270,11 @@ class ClusterUpgradeWaitGroup(TestGroup):
self.test_runner.run_cluster_upgrade_wait()
@test(depends_on=[cluster_upgrade_wait])
+ def verify_initial_configuration(self):
+ """Verify initial configuration values on the cluster."""
+ self.test_runner.run_verify_initial_configuration()
+
+ @test(depends_on=[cluster_upgrade_wait])
def verify_initial_cluster_data_after_upgrade(self):
"""Verify the initial data still exists after cluster upgrade."""
self.test_runner.run_verify_initial_cluster_data()
@@ -299,6 +328,11 @@ class ClusterShrinkWaitGroup(TestGroup):
self.test_runner.run_cluster_shrink_wait()
@test(depends_on=[cluster_shrink_wait])
+ def verify_initial_configuration(self):
+ """Verify initial configuration values on the cluster."""
+ self.test_runner.run_verify_initial_configuration()
+
+ @test(depends_on=[cluster_shrink_wait])
def verify_initial_cluster_data_after_shrink(self):
"""Verify the initial data still exists after cluster shrink."""
self.test_runner.run_verify_initial_cluster_data()
@@ -337,6 +371,93 @@ class ClusterRootEnableShrinkGroup(TestGroup):
@test(groups=[GROUP, groups.CLUSTER_ACTIONS,
+ groups.CLUSTER_ACTIONS_CFGGRP_ACTIONS],
+ depends_on_groups=[groups.CLUSTER_CREATE_WAIT],
+ runs_after_groups=[groups.CLUSTER_ACTIONS_ROOT_SHRINK])
+class ClusterConfigurationActionsGroup(TestGroup):
+
+ def __init__(self):
+ super(ClusterConfigurationActionsGroup, self).__init__(
+ ClusterRunnerFactory.instance())
+
+ @test
+ def detach_initial_configuration(self):
+ """Detach initial configuration group."""
+ self.test_runner.run_detach_initial_configuration()
+
+ @test(depends_on=[detach_initial_configuration])
+ def restart_cluster_after_detach(self):
+ """Restarting cluster after configuration change."""
+ self.test_runner.restart_after_configuration_change()
+
+ @test
+ def create_dynamic_configuration(self):
+ """Create a configuration group with only dynamic entries."""
+ self.test_runner.run_create_dynamic_configuration()
+
+ @test
+ def create_non_dynamic_configuration(self):
+ """Create a configuration group with only non-dynamic entries."""
+ self.test_runner.run_create_non_dynamic_configuration()
+
+ @test(depends_on=[create_dynamic_configuration,
+ restart_cluster_after_detach])
+ def attach_dynamic_configuration(self):
+ """Test attach dynamic group."""
+ self.test_runner.run_attach_dynamic_configuration()
+
+ @test(depends_on=[attach_dynamic_configuration])
+ def verify_dynamic_configuration(self):
+ """Verify dynamic values on the cluster."""
+ self.test_runner.run_verify_dynamic_configuration()
+
+ @test(depends_on=[attach_dynamic_configuration],
+ runs_after=[verify_dynamic_configuration])
+ def detach_dynamic_configuration(self):
+ """Test detach dynamic group."""
+ self.test_runner.run_detach_dynamic_configuration()
+
+ @test(depends_on=[create_non_dynamic_configuration,
+ detach_initial_configuration],
+ runs_after=[detach_dynamic_configuration])
+ def attach_non_dynamic_configuration(self):
+ """Test attach non-dynamic group."""
+ self.test_runner.run_attach_non_dynamic_configuration()
+
+ @test(depends_on=[attach_non_dynamic_configuration])
+ def restart_cluster_after_attach(self):
+ """Restarting cluster after configuration change."""
+ self.test_runner.restart_after_configuration_change()
+
+ @test(depends_on=[restart_cluster_after_attach])
+ def verify_non_dynamic_configuration(self):
+ """Verify non-dynamic values on the cluster."""
+ self.test_runner.run_verify_non_dynamic_configuration()
+
+ @test(depends_on=[attach_non_dynamic_configuration],
+ runs_after=[verify_non_dynamic_configuration])
+ def detach_non_dynamic_configuration(self):
+ """Test detach non-dynamic group."""
+ self.test_runner.run_detach_non_dynamic_configuration()
+
+ @test(runs_after=[detach_dynamic_configuration,
+ detach_non_dynamic_configuration])
+ def verify_initial_cluster_data(self):
+ """Verify the initial data still exists."""
+ self.test_runner.run_verify_initial_cluster_data()
+
+ @test(depends_on=[detach_dynamic_configuration])
+ def delete_dynamic_configuration(self):
+ """Test delete dynamic configuration group."""
+ self.test_runner.run_delete_dynamic_configuration()
+
+ @test(depends_on=[detach_non_dynamic_configuration])
+ def delete_non_dynamic_configuration(self):
+ """Test delete non-dynamic configuration group."""
+ self.test_runner.run_delete_non_dynamic_configuration()
+
+
+@test(groups=[GROUP, groups.CLUSTER_ACTIONS,
groups.CLUSTER_DELETE],
depends_on_groups=[groups.CLUSTER_CREATE_WAIT],
runs_after_groups=[groups.CLUSTER_ACTIONS_ROOT_ENABLE,
@@ -345,7 +466,9 @@ class ClusterRootEnableShrinkGroup(TestGroup):
groups.CLUSTER_ACTIONS_GROW_WAIT,
groups.CLUSTER_ACTIONS_SHRINK_WAIT,
groups.CLUSTER_UPGRADE_WAIT,
- groups.CLUSTER_ACTIONS_RESTART_WAIT])
+ groups.CLUSTER_ACTIONS_RESTART_WAIT,
+ groups.CLUSTER_CFGGRP_CREATE,
+ groups.CLUSTER_ACTIONS_CFGGRP_ACTIONS])
class ClusterDeleteGroup(TestGroup):
def __init__(self):
@@ -376,3 +499,19 @@ class ClusterDeleteWaitGroup(TestGroup):
def cluster_delete_wait(self):
"""Wait for the existing cluster to be gone."""
self.test_runner.run_cluster_delete_wait()
+
+
+@test(groups=[GROUP, groups.CLUSTER_ACTIONS,
+ groups.CLUSTER_CFGGRP_DELETE],
+ depends_on_groups=[groups.CLUSTER_CFGGRP_CREATE],
+ runs_after_groups=[groups.CLUSTER_DELETE_WAIT])
+class ClusterConfigurationDeleteGroup(TestGroup):
+
+ def __init__(self):
+ super(ClusterConfigurationDeleteGroup, self).__init__(
+ ClusterRunnerFactory.instance())
+
+ @test
+ def delete_initial_configuration(self):
+ """Delete initial configuration group."""
+ self.test_runner.run_delete_initial_configuration()
diff --git a/trove/tests/scenario/runners/cluster_runners.py b/trove/tests/scenario/runners/cluster_runners.py
index d9a0f153..8c652ec6 100644
--- a/trove/tests/scenario/runners/cluster_runners.py
+++ b/trove/tests/scenario/runners/cluster_runners.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import os
from proboscis import SkipTest
@@ -49,6 +50,11 @@ class ClusterRunner(TestRunner):
self.initial_instance_count = None
self.cluster_instances = None
self.cluster_removed_instances = None
+ self.active_config_group_id = None
+ self.config_requires_restart = False
+ self.initial_group_id = None
+ self.dynamic_group_id = None
+ self.non_dynamic_group_id = None
@property
def is_using_existing_cluster(self):
@@ -62,6 +68,15 @@ class ClusterRunner(TestRunner):
def min_cluster_node_count(self):
return 2
+ def run_initial_configuration_create(self, expected_http_code=200):
+ group_id, requires_restart = self.create_initial_configuration(
+ expected_http_code)
+ if group_id:
+ self.initial_group_id = group_id
+ self.config_requires_restart = requires_restart
+ else:
+ raise SkipTest("No groups defined.")
+
def run_cluster_create(self, num_nodes=None, expected_task_name='BUILDING',
expected_http_code=200):
self.cluster_count_before_create = len(
@@ -84,11 +99,11 @@ class ClusterRunner(TestRunner):
self.cluster_id = self.assert_cluster_create(
self.cluster_name, instance_defs, self.locality,
- expected_task_name, expected_http_code)
+ self.initial_group_id, expected_task_name, expected_http_code)
def assert_cluster_create(
- self, cluster_name, instances_def, locality, expected_task_name,
- expected_http_code):
+ self, cluster_name, instances_def, locality, configuration,
+ expected_task_name, expected_http_code):
self.report.log("Testing cluster create: %s" % cluster_name)
@@ -100,8 +115,10 @@ class ClusterRunner(TestRunner):
cluster = client.clusters.create(
cluster_name, self.instance_info.dbaas_datastore,
self.instance_info.dbaas_datastore_version,
- instances=instances_def, locality=locality)
+ instances=instances_def, locality=locality,
+ configuration=configuration)
self.assert_client_code(client, expected_http_code)
+ self.active_config_group_id = configuration
self._assert_cluster_values(cluster, expected_task_name)
for instance in cluster.instances:
self.register_debug_inst_ids(instance['id'])
@@ -202,7 +219,8 @@ class ClusterRunner(TestRunner):
self.current_root_creds = client.root.create_cluster_root(
self.cluster_id, root_credentials['password'])
self.assert_client_code(client, expected_http_code)
- self._assert_cluster_response(client, cluster_id, expected_task_name)
+ self._assert_cluster_response(
+ client, self.cluster_id, expected_task_name)
self.assert_equal(root_credentials['name'],
self.current_root_creds[0])
self.assert_equal(root_credentials['password'],
@@ -506,6 +524,8 @@ class ClusterRunner(TestRunner):
check.has_field("updated", six.text_type)
if check_locality:
check.has_field("locality", six.text_type)
+ if self.active_config_group_id:
+ check.has_field("configuration", six.text_type)
for instance in cluster.instances:
isinstance(instance, dict)
self.assert_is_not_none(instance['id'])
@@ -528,6 +548,214 @@ class ClusterRunner(TestRunner):
except exceptions.NotFound:
self.assert_client_code(client, 404)
+ def restart_after_configuration_change(self):
+ if self.config_requires_restart:
+ self.run_cluster_restart()
+ self.run_cluster_restart_wait()
+ self.config_requires_restart = False
+ else:
+ raise SkipTest("Not required.")
+
+ def run_create_dynamic_configuration(self, expected_http_code=200):
+ values = self.test_helper.get_dynamic_group()
+ if values:
+ self.dynamic_group_id = self.assert_create_group(
+ 'dynamic_cluster_test_group',
+ 'a fully dynamic group should not require restart',
+ values, expected_http_code)
+ elif values is None:
+ raise SkipTest("No dynamic group defined in %s." %
+ self.test_helper.get_class_name())
+ else:
+ raise SkipTest("Datastore has no dynamic configuration values.")
+
+ def assert_create_group(self, name, description, values,
+ expected_http_code):
+ json_def = json.dumps(values)
+ client = self.auth_client
+ result = client.configurations.create(
+ name,
+ json_def,
+ description,
+ datastore=self.instance_info.dbaas_datastore,
+ datastore_version=self.instance_info.dbaas_datastore_version)
+ self.assert_client_code(client, expected_http_code)
+
+ return result.id
+
+ def run_create_non_dynamic_configuration(self, expected_http_code=200):
+ values = self.test_helper.get_non_dynamic_group()
+ if values:
+ self.non_dynamic_group_id = self.assert_create_group(
+ 'non_dynamic_cluster_test_group',
+ 'a group containing non-dynamic properties should always '
+ 'require restart',
+ values, expected_http_code)
+ elif values is None:
+ raise SkipTest("No non-dynamic group defined in %s." %
+ self.test_helper.get_class_name())
+ else:
+ raise SkipTest("Datastore has no non-dynamic configuration "
+ "values.")
+
+ def run_attach_dynamic_configuration(
+ self, expected_states=['NONE'],
+ expected_http_code=202):
+ if self.dynamic_group_id:
+ self.assert_attach_configuration(
+ self.cluster_id, self.dynamic_group_id, expected_states,
+ expected_http_code)
+
+ def assert_attach_configuration(
+ self, cluster_id, group_id, expected_states, expected_http_code,
+ restart_inst=False):
+ client = self.auth_client
+ client.clusters.configuration_attach(cluster_id, group_id)
+ self.assert_client_code(client, expected_http_code)
+ self.active_config_group_id = group_id
+ self._assert_cluster_states(client, cluster_id, expected_states)
+ self.assert_configuration_group(client, cluster_id, group_id)
+
+ if restart_inst:
+ self.config_requires_restart = True
+ cluster_instances = self._get_cluster_instances(cluster_id)
+ for node in cluster_instances:
+ self.assert_equal(
+ 'RESTART_REQUIRED', node.status,
+ "Node '%s' should be in 'RESTART_REQUIRED' state."
+ % node.id)
+
+ def assert_configuration_group(
+ self, client, cluster_id, expected_group_id):
+ cluster = client.clusters.get(cluster_id)
+ self.assert_equal(
+ expected_group_id, cluster.configuration,
+ "Attached group does not have the expected ID.")
+
+ cluster_instances = self._get_cluster_instances(client, cluster_id)
+ for node in cluster_instances:
+ self.assert_equal(
+ expected_group_id, cluster.configuration,
+ "Attached group does not have the expected ID on "
+ "cluster node: %s" % node.id)
+
+ def run_attach_non_dynamic_configuration(
+ self, expected_states=['NONE'],
+ expected_http_code=202):
+ if self.non_dynamic_group_id:
+ self.assert_attach_configuration(
+ self.cluster_id, self.non_dynamic_group_id,
+ expected_states, expected_http_code, restart_inst=True)
+
+ def run_verify_initial_configuration(self):
+ if self.initial_group_id:
+ self.verify_configuration(self.cluster_id, self.initial_group_id)
+
+ def verify_configuration(self, cluster_id, expected_group_id):
+ self.assert_configuration_group(cluster_id, expected_group_id)
+ self.assert_configuration_values(cluster_id, expected_group_id)
+
+ def assert_configuration_values(self, cluster_id, group_id):
+ if group_id == self.initial_group_id:
+ if not self.config_requires_restart:
+ expected_configs = self.test_helper.get_dynamic_group()
+ else:
+ expected_configs = self.test_helper.get_non_dynamic_group()
+ if group_id == self.dynamic_group_id:
+ expected_configs = self.test_helper.get_dynamic_group()
+ elif group_id == self.non_dynamic_group_id:
+ expected_configs = self.test_helper.get_non_dynamic_group()
+
+ self._assert_configuration_values(cluster_id, expected_configs)
+
+ def _assert_configuration_values(self, cluster_id, expected_configs):
+ cluster_instances = self._get_cluster_instances(cluster_id)
+ for node in cluster_instances:
+ host = self.get_instance_host(node)
+ self.report.log(
+ "Verifying cluster configuration via node: %s" % host)
+ for name, value in expected_configs.items():
+ actual = self.test_helper.get_configuration_value(name, host)
+ self.assert_equal(str(value), str(actual),
+ "Unexpected value of property '%s'" % name)
+
+ def run_verify_dynamic_configuration(self):
+ if self.dynamic_group_id:
+ self.verify_configuration(self.cluster_id, self.dynamic_group_id)
+
+ def run_verify_non_dynamic_configuration(self):
+ if self.non_dynamic_group_id:
+ self.verify_configuration(
+ self.cluster_id, self.non_dynamic_group_id)
+
+ def run_detach_initial_configuration(self, expected_states=['NONE'],
+ expected_http_code=202):
+ if self.initial_group_id:
+ self.assert_detach_configuration(
+ self.cluster_id, expected_states, expected_http_code,
+ restart_inst=self.config_requires_restart)
+
+ def run_detach_dynamic_configuration(self, expected_states=['NONE'],
+ expected_http_code=202):
+ if self.dynamic_group_id:
+ self.assert_detach_configuration(
+ self.cluster_id, expected_states, expected_http_code)
+
+ def assert_detach_configuration(
+ self, cluster_id, expected_states, expected_http_code,
+ restart_inst=False):
+ client = self.auth_client
+ client.clusters.configuration_detach(cluster_id)
+ self.assert_client_code(client, expected_http_code)
+ self.active_config_group_id = None
+ self._assert_cluster_states(client, cluster_id, expected_states)
+ cluster = client.clusters.get(cluster_id)
+ self.assert_false(
+ hasattr(cluster, 'configuration'),
+ "Configuration group was not detached from the cluster.")
+
+ cluster_instances = self._get_cluster_instances(client, cluster_id)
+ for node in cluster_instances:
+ self.assert_false(
+ hasattr(node, 'configuration'),
+ "Configuration group was not detached from cluster node: %s"
+ % node.id)
+
+ if restart_inst:
+ self.config_requires_restart = True
+ cluster_instances = self._get_cluster_instances(client, cluster_id)
+ for node in cluster_instances:
+ self.assert_equal(
+ 'RESTART_REQUIRED', node.status,
+ "Node '%s' should be in 'RESTART_REQUIRED' state."
+ % node.id)
+
+ def run_detach_non_dynamic_configuration(
+ self, expected_states=['NONE'],
+ expected_http_code=202):
+ if self.non_dynamic_group_id:
+ self.assert_detach_configuration(
+ self.cluster_id, expected_states, expected_http_code,
+ restart_inst=True)
+
+ def run_delete_initial_configuration(self, expected_http_code=202):
+ if self.initial_group_id:
+ self.assert_group_delete(self.initial_group_id, expected_http_code)
+
+ def assert_group_delete(self, group_id, expected_http_code):
+ client = self.auth_client
+ client.configurations.delete(group_id)
+ self.assert_client_code(client, expected_http_code)
+
+ def run_delete_dynamic_configuration(self, expected_http_code=202):
+ if self.dynamic_group_id:
+ self.assert_group_delete(self.dynamic_group_id, expected_http_code)
+
+ def run_delete_non_dynamic_configuration(self, expected_http_code=202):
+ if self.non_dynamic_group_id:
+ self.assert_group_delete(self.non_dynamic_group_id,
+ expected_http_code)
+
class CassandraClusterRunner(ClusterRunner):
diff --git a/trove/tests/scenario/runners/instance_create_runners.py b/trove/tests/scenario/runners/instance_create_runners.py
index eb4bc259..3e9e6484 100644
--- a/trove/tests/scenario/runners/instance_create_runners.py
+++ b/trove/tests/scenario/runners/instance_create_runners.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-
from proboscis import SkipTest
from trove.tests.config import CONFIG
@@ -62,21 +60,9 @@ class InstanceCreateRunner(TestRunner):
self.instance_info.helper_database = instance_info.helper_database
def run_initial_configuration_create(self, expected_http_code=200):
- dynamic_config = self.test_helper.get_dynamic_group()
- non_dynamic_config = self.test_helper.get_non_dynamic_group()
- values = dynamic_config or non_dynamic_config
- if values:
- json_def = json.dumps(values)
- client = self.auth_client
- result = client.configurations.create(
- 'initial_configuration_for_instance_create',
- json_def,
- "Configuration group used by instance create tests.",
- datastore=self.instance_info.dbaas_datastore,
- datastore_version=self.instance_info.dbaas_datastore_version)
- self.assert_client_code(client, expected_http_code)
-
- self.config_group_id = result.id
+ group_id, _ = self.create_initial_configuration(expected_http_code)
+ if group_id:
+ self.config_group_id = group_id
else:
raise SkipTest("No groups defined.")
diff --git a/trove/tests/scenario/runners/test_runners.py b/trove/tests/scenario/runners/test_runners.py
index bc9292d4..3699b087 100644
--- a/trove/tests/scenario/runners/test_runners.py
+++ b/trove/tests/scenario/runners/test_runners.py
@@ -15,6 +15,7 @@
import datetime
import inspect
+import json
import netaddr
import os
import proboscis
@@ -903,6 +904,25 @@ class TestRunner(object):
full_list = client.databases.list(instance_id)
return {database.name: database for database in full_list}
+ def create_initial_configuration(self, expected_http_code):
+ client = self.auth_client
+ dynamic_config = self.test_helper.get_dynamic_group()
+ non_dynamic_config = self.test_helper.get_non_dynamic_group()
+ values = dynamic_config or non_dynamic_config
+ if values:
+ json_def = json.dumps(values)
+ result = client.configurations.create(
+ 'initial_configuration_for_create_tests',
+ json_def,
+ "Configuration group used by create tests.",
+ datastore=self.instance_info.dbaas_datastore,
+ datastore_version=self.instance_info.dbaas_datastore_version)
+ self.assert_client_code(client, expected_http_code)
+
+ return (result.id, dynamic_config is None)
+
+ return (None, False)
+
class CheckInstance(AttrCheck):
"""Class to check various attributes of Instance details."""
diff --git a/trove/tests/unittests/cluster/test_cassandra_cluster.py b/trove/tests/unittests/cluster/test_cassandra_cluster.py
index 4c7a456f..336e9aa7 100644
--- a/trove/tests/unittests/cluster/test_cassandra_cluster.py
+++ b/trove/tests/unittests/cluster/test_cassandra_cluster.py
@@ -53,7 +53,7 @@ class ClusterTest(trove_testtools.TestCase):
CassandraCluster._create_cluster_instances(
self.context, 'test_cluster_id', 'test_cluster',
datastore, datastore_version,
- test_instances, None, None)
+ test_instances, None, None, None)
check_quotas.assert_called_once_with(
ANY, instances=num_instances, volumes=get_vol_size.return_value)
diff --git a/trove/tests/unittests/cluster/test_cluster.py b/trove/tests/unittests/cluster/test_cluster.py
index 8eee1ed8..9f391af5 100644
--- a/trove/tests/unittests/cluster/test_cluster.py
+++ b/trove/tests/unittests/cluster/test_cluster.py
@@ -81,8 +81,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
[],
- None, None
- )
+ None, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_unequal_flavors(self, mock_client):
@@ -95,8 +94,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_unequal_volumes(self,
@@ -112,8 +110,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_storage_not_specified(self,
@@ -142,8 +139,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch('trove.cluster.models.LOG')
def test_delete_bad_task_status(self, mock_logging):
diff --git a/trove/tests/unittests/cluster/test_cluster_controller.py b/trove/tests/unittests/cluster/test_cluster_controller.py
index 107c8cd5..c5b7360c 100644
--- a/trove/tests/unittests/cluster/test_cluster_controller.py
+++ b/trove/tests/unittests/cluster/test_cluster_controller.py
@@ -195,7 +195,7 @@ class TestClusterController(trove_testtools.TestCase):
mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version,
instances, {},
- self.locality)
+ self.locality, None)
@patch.object(Cluster, 'load')
def test_show_cluster(self,
diff --git a/trove/tests/unittests/cluster/test_cluster_pxc_controller.py b/trove/tests/unittests/cluster/test_cluster_pxc_controller.py
index 51176aa2..c0b253d9 100644
--- a/trove/tests/unittests/cluster/test_cluster_pxc_controller.py
+++ b/trove/tests/unittests/cluster/test_cluster_pxc_controller.py
@@ -159,7 +159,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
@patch.object(Cluster, 'load')
def test_show_cluster(self,
diff --git a/trove/tests/unittests/cluster/test_cluster_redis_controller.py b/trove/tests/unittests/cluster/test_cluster_redis_controller.py
index 6fe3cfc7..70c7a1a2 100644
--- a/trove/tests/unittests/cluster/test_cluster_redis_controller.py
+++ b/trove/tests/unittests/cluster/test_cluster_redis_controller.py
@@ -196,7 +196,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
@patch.object(Cluster, 'load')
def test_show_cluster(self,
diff --git a/trove/tests/unittests/cluster/test_cluster_vertica_controller.py b/trove/tests/unittests/cluster/test_cluster_vertica_controller.py
index 720f5a2e..f7d8515e 100644
--- a/trove/tests/unittests/cluster/test_cluster_vertica_controller.py
+++ b/trove/tests/unittests/cluster/test_cluster_vertica_controller.py
@@ -159,7 +159,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
@patch.object(Cluster, 'load')
def test_show_cluster(self,
diff --git a/trove/tests/unittests/cluster/test_galera_cluster.py b/trove/tests/unittests/cluster/test_galera_cluster.py
index aea712d8..ba8c76a3 100644
--- a/trove/tests/unittests/cluster/test_galera_cluster.py
+++ b/trove/tests/unittests/cluster/test_galera_cluster.py
@@ -83,8 +83,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- [], {}, None
- )
+ [], {}, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_flavor_not_specified(self, mock_client):
@@ -96,8 +95,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_invalid_flavor_specified(self,
@@ -116,8 +114,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_volume_no_specified(self,
@@ -132,8 +129,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(remote, 'create_nova_client')
@patch.object(galera_api, 'CONF')
@@ -152,8 +148,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(remote, 'create_nova_client')
@patch.object(galera_api, 'CONF')
@@ -184,8 +179,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(remote, 'create_nova_client')
def test_create_volume_not_equal(self, mock_client):
@@ -199,8 +193,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None
- )
+ instances, {}, None, None)
@patch.object(inst_models.DBInstance, 'find_all')
@patch.object(inst_models.Instance, 'create')
@@ -219,7 +212,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
@@ -241,7 +234,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(4, mock_ins_create.call_count)
@@ -279,7 +272,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- instances, {}, None)
+ instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
diff --git a/trove/tests/unittests/cluster/test_redis_cluster.py b/trove/tests/unittests/cluster/test_redis_cluster.py
index 134b450c..4710b9a4 100644
--- a/trove/tests/unittests/cluster/test_redis_cluster.py
+++ b/trove/tests/unittests/cluster/test_redis_cluster.py
@@ -91,7 +91,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
self.instances_w_volumes,
- {}, None)
+ {}, None, None)
@patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF')
@@ -105,7 +105,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
self.instances_no_volumes,
- {}, None)
+ {}, None, None)
@patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF')
@@ -122,7 +122,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
self.instances_w_volumes,
- {}, None)
+ {}, None, None)
@patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF')
@@ -151,7 +151,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
self.instances_no_volumes,
- {}, None)
+ {}, None, None)
@patch.object(redis_api, 'CONF')
@patch.object(inst_models.Instance, 'create')
@@ -167,7 +167,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- self.instances_w_volumes, {}, None)
+ self.instances_w_volumes, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
self.dbcreate_mock.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
@@ -199,7 +199,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- self.instances_no_volumes, {}, None)
+ self.instances_no_volumes, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
self.dbcreate_mock.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
diff --git a/trove/tests/unittests/cluster/test_vertica_cluster.py b/trove/tests/unittests/cluster/test_vertica_cluster.py
index 396bac93..48abafa4 100644
--- a/trove/tests/unittests/cluster/test_vertica_cluster.py
+++ b/trove/tests/unittests/cluster/test_vertica_cluster.py
@@ -80,7 +80,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name,
self.datastore,
self.datastore_version,
- [], None, None)
+ [], None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -95,8 +95,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -118,8 +117,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -137,8 +135,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -162,8 +159,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -199,8 +195,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all')
@@ -218,8 +213,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None
- )
+ None, None, None)
@patch.object(inst_models.DBInstance, 'find_all')
@patch.object(inst_models.Instance, 'create')
@@ -237,7 +231,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None)
+ None, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
@@ -276,7 +270,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore,
self.datastore_version,
instances,
- None, None)
+ None, None, None)
mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count)
diff --git a/trove/tests/unittests/instance/test_instance_controller.py b/trove/tests/unittests/instance/test_instance_controller.py
index cbd820bc..00aabe2b 100644
--- a/trove/tests/unittests/instance/test_instance_controller.py
+++ b/trove/tests/unittests/instance/test_instance_controller.py
@@ -257,8 +257,8 @@ class TestInstanceController(trove_testtools.TestCase):
def _setup_modify_instance_mocks(self):
instance = Mock()
instance.detach_replica = Mock()
- instance.assign_configuration = Mock()
- instance.unassign_configuration = Mock()
+ instance.attach_configuration = Mock()
+ instance.detach_configuration = Mock()
instance.update_db = Mock()
return instance
@@ -270,8 +270,8 @@ class TestInstanceController(trove_testtools.TestCase):
instance, **args)
self.assertEqual(0, instance.detach_replica.call_count)
- self.assertEqual(0, instance.unassign_configuration.call_count)
- self.assertEqual(0, instance.assign_configuration.call_count)
+ self.assertEqual(0, instance.detach_configuration.call_count)
+ self.assertEqual(0, instance.attach_configuration.call_count)
self.assertEqual(0, instance.update_db.call_count)
def test_modify_instance_with_nonempty_args_calls_update_db(self):
@@ -312,7 +312,7 @@ class TestInstanceController(trove_testtools.TestCase):
self.controller._modify_instance(self.context, self.req,
instance, **args)
- self.assertEqual(1, instance.assign_configuration.call_count)
+ self.assertEqual(1, instance.attach_configuration.call_count)
def test_modify_instance_with_None_configuration_id_arg(self):
instance = self._setup_modify_instance_mocks()
@@ -322,7 +322,7 @@ class TestInstanceController(trove_testtools.TestCase):
self.controller._modify_instance(self.context, self.req,
instance, **args)
- self.assertEqual(1, instance.unassign_configuration.call_count)
+ self.assertEqual(1, instance.detach_configuration.call_count)
def test_modify_instance_with_all_args(self):
instance = self._setup_modify_instance_mocks()
@@ -334,5 +334,5 @@ class TestInstanceController(trove_testtools.TestCase):
instance, **args)
self.assertEqual(1, instance.detach_replica.call_count)
- self.assertEqual(1, instance.assign_configuration.call_count)
+ self.assertEqual(1, instance.attach_configuration.call_count)
instance.update_db.assert_called_once_with(**args)