Add configuration support for clusters

Implement configuration attach and detach API for clusters.

Implement rolling strategy for applying configuration changes
(both attach and detach follow the same pattern).

1. Persist the changes on all nodes (leaving nodes in RESTART_REQUIRED state).
2. Update Trove records.
3. Apply changes dynamically via one or all node(s) if possible
   (and remove RESTART_REQUIRED flag from all nodes).

Notes:

  The single instance implementation has been restructured (similar to above)
  such that it always leaves the instance in one of the three states:

    a) Unchanged
    b) Changes persisted but not applied
       (Instance has configuration attached but requires restart.
        It is safe restart manually or detach the group to avoid
        any changes)
    c) Changes persisted and applied (if possible)

  This implemenation should always leave the cluster (and each instance)
  in a consistent state.
  Runtime configuration will not be changed until it is first persisted
  on all nodes.

  If there is a failure during step 1) the cluster is still running
  the old configuration. Some instances may have new configuration
  persisted, but not applied.
  The cluster will not have configuration attached unless it can
  be applied to all nodes.
  The individual nodes will have configuration attached as soon as it is
  persisted on the guest.
  It is safe to retry, reapplying the same configuration on a node is
  noop.
  It is safe to detach. Removing configuration from nodes without one
  is a noop.
  It is safe to detach the configuration from individual nodes via
  single-instance API.
  It is safe to attach the configuration to remaining nodes via
  single-instance API and rerun cluster attach to update Trove records.

  If 3) fails for whatewer reason the instances are left
  in RESTART_REQUIRED state.
  It is safe to retry or detach configuration or restart the
  instances manually.

Also fixed various minor cluster issues.

Implements: blueprint cluster-configuration-groups
Change-Id: I7c0a22c6a0287128d0c37e100589c78173fd9c1a
This commit is contained in:
Petr Malik 2016-10-26 18:01:41 -04:00
parent 6e7fa196dc
commit 9bca402ec3
35 changed files with 911 additions and 214 deletions

View File

@ -0,0 +1,3 @@
features:
- Support attaching and detaching of configuration
groups on clusters.

View File

@ -480,13 +480,13 @@
[ [
"trove/configuration/service.py", "trove/configuration/service.py",
"E1101", "E1101",
"Instance of 'BuiltInstance' has no 'update_overrides' member", "Instance of 'BuiltInstance' has no 'update_configuration' member",
"ConfigurationsController._refresh_on_all_instances" "ConfigurationsController._refresh_on_all_instances"
], ],
[ [
"trove/configuration/service.py", "trove/configuration/service.py",
"no-member", "no-member",
"Instance of 'BuiltInstance' has no 'update_overrides' member", "Instance of 'BuiltInstance' has no 'update_configuration' member",
"ConfigurationsController._refresh_on_all_instances" "ConfigurationsController._refresh_on_all_instances"
], ],
[ [
@ -741,6 +741,18 @@
"Instance of 'Table' has no 'create_column' member", "Instance of 'Table' has no 'create_column' member",
"upgrade" "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", "trove/db/sqlalchemy/migration.py",
"E0611", "E0611",
@ -1287,18 +1299,6 @@
"Instance of 'BuiltInstance' has no 'restart' member", "Instance of 'BuiltInstance' has no 'restart' member",
"Manager.restart" "Manager.restart"
], ],
[
"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", "trove/taskmanager/manager.py",
"E1101", "E1101",
@ -1371,18 +1371,6 @@
"Instance of 'BuiltInstance' has no 'restart' member", "Instance of 'BuiltInstance' has no 'restart' member",
"Manager.restart" "Manager.restart"
], ],
[
"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", "trove/taskmanager/manager.py",
"no-member", "no-member",

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import six
from oslo_log import log as logging from oslo_log import log as logging
from novaclient import exceptions as nova_exceptions 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 cfg
from trove.common import exception from trove.common import exception
from trove.common.i18n import _ from trove.common.i18n import _
from trove.common.notification import (DBaaSClusterGrow, DBaaSClusterShrink, from trove.common.notification import (
DBaaSClusterResetStatus, DBaaSClusterAttachConfiguration,
DBaaSClusterRestart) DBaaSClusterDetachConfiguration,
DBaaSClusterGrow,
DBaaSClusterShrink,
DBaaSClusterResetStatus,
DBaaSClusterRestart)
from trove.common.notification import DBaaSClusterUpgrade 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.notification import StartNotification
from trove.common import remote from trove.common import remote
from trove.common import server_group as srv_grp from trove.common import server_group as srv_grp
from trove.common.strategies.cluster import strategy from trove.common.strategies.cluster import strategy
from trove.common import utils from trove.common import utils
from trove.configuration import models as config_models
from trove.datastore import models as datastore_models from trove.datastore import models as datastore_models
from trove.db import models as dbmodels from trove.db import models as dbmodels
from trove.instance import models as inst_models from trove.instance import models as inst_models
from trove.instance.tasks import InstanceTasks
from trove.taskmanager import api as task_api from trove.taskmanager import api as task_api
@ -49,7 +60,7 @@ def persisted_models():
class DBCluster(dbmodels.DatabaseModelBase): class DBCluster(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'created', 'updated', 'name', 'task_id', _data_fields = ['id', 'created', 'updated', 'name', 'task_id',
'tenant_id', 'datastore_version_id', 'deleted', 'tenant_id', 'datastore_version_id', 'deleted',
'deleted_at'] 'deleted_at', 'configuration_id']
def __init__(self, task_status, **kwargs): def __init__(self, task_status, **kwargs):
""" """
@ -140,7 +151,6 @@ class Cluster(object):
self.update_db(task_status=ClusterTasks.NONE) self.update_db(task_status=ClusterTasks.NONE)
def reset_status(self): def reset_status(self):
self.validate_cluster_available([ClusterTasks.BUILDING_INITIAL])
LOG.info(_("Resetting status to NONE on cluster %s") % self.id) LOG.info(_("Resetting status to NONE on cluster %s") % self.id)
self.reset_task() self.reset_task()
instances = inst_models.DBInstance.find_all(cluster_id=self.id, instances = inst_models.DBInstance.find_all(cluster_id=self.id,
@ -197,6 +207,10 @@ class Cluster(object):
def deleted_at(self): def deleted_at(self):
return self.db_info.deleted_at return self.db_info.deleted_at
@property
def configuration_id(self):
return self.db_info.configuration_id
@property @property
def db_instances(self): def db_instances(self):
"""DBInstance objects are persistent, therefore cacheable.""" """DBInstance objects are persistent, therefore cacheable."""
@ -246,14 +260,14 @@ class Cluster(object):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, def create(cls, context, name, datastore, datastore_version,
instances, extended_properties, locality): instances, extended_properties, locality, configuration):
locality = srv_grp.ServerGroup.build_scheduler_hint( locality = srv_grp.ServerGroup.build_scheduler_hint(
context, locality, name) context, locality, name)
api_strategy = strategy.load_api_strategy(datastore_version.manager) api_strategy = strategy.load_api_strategy(datastore_version.manager)
return api_strategy.cluster_class.create(context, name, datastore, return api_strategy.cluster_class.create(context, name, datastore,
datastore_version, instances, datastore_version, instances,
extended_properties, extended_properties,
locality) locality, configuration)
def validate_cluster_available(self, valid_states=[ClusterTasks.NONE]): def validate_cluster_available(self, valid_states=[ClusterTasks.NONE]):
if self.db_info.task_status not in valid_states: if self.db_info.task_status not in valid_states:
@ -304,6 +318,11 @@ class Cluster(object):
if 'availability_zone' in node: if 'availability_zone' in node:
instance['availability_zone'] = ( instance['availability_zone'] = (
node['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) instances.append(instance)
return self.grow(instances) return self.grow(instances)
elif action == 'shrink': elif action == 'shrink':
@ -328,7 +347,23 @@ class Cluster(object):
dv = datastore_models.DatastoreVersion.load(self.datastore, dv_id) dv = datastore_models.DatastoreVersion.load(self.datastore, dv_id)
with StartNotification(context, cluster_id=self.id, with StartNotification(context, cluster_id=self.id,
datastore_version=dv.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: else:
raise exception.BadRequest(_("Action %s not supported") % action) raise exception.BadRequest(_("Action %s not supported") % action)
@ -376,6 +411,128 @@ class Cluster(object):
def upgrade(self, datastore_version): def upgrade(self, datastore_version):
raise exception.BadRequest(_("Action 'upgrade' not supported")) 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 @staticmethod
def load_instance(context, cluster_id, instance_id): def load_instance(context, cluster_id, instance_id):
return inst_models.load_instance_with_info( return inst_models.load_instance_with_info(

View File

@ -218,6 +218,8 @@ class ClusterController(wsgi.Controller):
if locality not in locality_domain: if locality not in locality_domain:
raise exception.BadRequest(msg=locality_domain_msg) raise exception.BadRequest(msg=locality_domain_msg)
configuration = body['cluster'].get('configuration')
context.notification = notification.DBaaSClusterCreate(context, context.notification = notification.DBaaSClusterCreate(context,
request=req) request=req)
with StartNotification(context, name=name, datastore=datastore.name, with StartNotification(context, name=name, datastore=datastore.name,
@ -225,7 +227,7 @@ class ClusterController(wsgi.Controller):
cluster = models.Cluster.create(context, name, datastore, cluster = models.Cluster.create(context, name, datastore,
datastore_version, instances, datastore_version, instances,
extended_properties, extended_properties,
locality) locality, configuration)
cluster.locality = locality cluster.locality = locality
view = views.load_view(cluster, req=req, load_servers=False) view = views.load_view(cluster, req=req, load_servers=False)
return wsgi.Result(view.data(), 200) return wsgi.Result(view.data(), 200)

View File

@ -73,6 +73,8 @@ class ClusterTasks(object):
0x07, 'UPGRADING_CLUSTER', 'Upgrading the cluster to new version.') 0x07, 'UPGRADING_CLUSTER', 'Upgrading the cluster to new version.')
RESTARTING_CLUSTER = ClusterTask( RESTARTING_CLUSTER = ClusterTask(
0x08, 'RESTARTING_CLUSTER', 'Restarting the cluster.') 0x08, 'RESTARTING_CLUSTER', 'Restarting the cluster.')
UPDATING_CLUSTER = ClusterTask(
0x09, 'UPDATING_CLUSTER', 'Updating cluster configuration.')
# Dissuade further additions at run-time. # Dissuade further additions at run-time.

View File

@ -55,6 +55,8 @@ class ClusterView(object):
if self.cluster.locality: if self.cluster.locality:
cluster_dict['locality'] = 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) LOG.debug(cluster_dict)
return {"cluster": cluster_dict} return {"cluster": cluster_dict}

View File

@ -236,6 +236,11 @@ class UnprocessableEntity(TroveError):
message = _("Unable to process the contained request.") message = _("Unable to process the contained request.")
class ConfigurationNotSupported(UnprocessableEntity):
message = _("Configuration groups not supported by the datastore.")
class CannotResizeToSameSize(TroveError): class CannotResizeToSameSize(TroveError):
message = _("No change was requested in the size of the instance.") message = _("No change was requested in the size of the instance.")

View File

@ -549,6 +549,28 @@ class DBaaSInstanceDetachConfiguration(DBaaSAPINotification):
return ['instance_id'] 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): class DBaaSClusterCreate(DBaaSAPINotification):
@abc.abstractmethod @abc.abstractmethod

View File

@ -82,19 +82,20 @@ class CassandraCluster(models.Cluster):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, 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.") LOG.debug("Processing a request for creating a new cluster.")
# Updating Cluster Task. # Updating Cluster Task.
db_info = models.DBCluster.create( db_info = models.DBCluster.create(
name=name, tenant_id=context.tenant, name=name, tenant_id=context.tenant,
datastore_version_id=datastore_version.id, datastore_version_id=datastore_version.id,
task_status=ClusterTasks.BUILDING_INITIAL) task_status=ClusterTasks.BUILDING_INITIAL,
configuration_id=configuration)
cls._create_cluster_instances( cls._create_cluster_instances(
context, db_info.id, db_info.name, context, db_info.id, db_info.name,
datastore, datastore_version, instances, extended_properties, datastore, datastore_version, instances, extended_properties,
locality) locality, configuration)
# Calling taskmanager to further proceed for cluster-configuration. # Calling taskmanager to further proceed for cluster-configuration.
task_api.load(context, datastore_version.manager).create_cluster( task_api.load(context, datastore_version.manager).create_cluster(
@ -106,7 +107,7 @@ class CassandraCluster(models.Cluster):
def _create_cluster_instances( def _create_cluster_instances(
cls, context, cluster_id, cluster_name, cls, context, cluster_id, cluster_name,
datastore, datastore_version, instances, extended_properties, datastore, datastore_version, instances, extended_properties,
locality): locality, configuration_id):
LOG.debug("Processing a request for new cluster instances.") LOG.debug("Processing a request for new cluster instances.")
cassandra_conf = CONF.get(datastore_version.manager) cassandra_conf = CONF.get(datastore_version.manager)
@ -153,7 +154,7 @@ class CassandraCluster(models.Cluster):
instance['volume_size'], None, instance['volume_size'], None,
nics=instance.get('nics', None), nics=instance.get('nics', None),
availability_zone=instance_az, availability_zone=instance_az,
configuration_id=None, configuration_id=configuration_id,
cluster_config=member_config, cluster_config=member_config,
modules=instance.get('modules'), modules=instance.get('modules'),
locality=locality, locality=locality,
@ -180,9 +181,11 @@ class CassandraCluster(models.Cluster):
db_info.update(task_status=ClusterTasks.GROWING_CLUSTER) db_info.update(task_status=ClusterTasks.GROWING_CLUSTER)
locality = srv_grp.ServerGroup.convert_to_hint(self.server_group) locality = srv_grp.ServerGroup.convert_to_hint(self.server_group)
configuration_id = self.db_info.configuration_id
new_instances = self._create_cluster_instances( new_instances = self._create_cluster_instances(
context, db_info.id, db_info.name, datastore, datastore_version, 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( task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances]) db_info.id, [instance.id for instance in new_instances])
@ -212,6 +215,12 @@ class CassandraCluster(models.Cluster):
def upgrade(self, datastore_version): def upgrade(self, datastore_version):
self.rolling_upgrade(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): class CassandraClusterView(ClusterView):

View File

@ -97,7 +97,8 @@ class GaleraCommonCluster(cluster_models.Cluster):
@staticmethod @staticmethod
def _create_instances(context, db_info, datastore, datastore_version, 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, member_config = {"id": db_info.id,
"instance_type": "member"} "instance_type": "member"}
name_index = 1 name_index = 1
@ -118,7 +119,7 @@ class GaleraCommonCluster(cluster_models.Cluster):
availability_zone=instance.get( availability_zone=instance.get(
'availability_zone', None), 'availability_zone', None),
nics=instance.get('nics', None), nics=instance.get('nics', None),
configuration_id=None, configuration_id=configuration_id,
cluster_config=member_config, cluster_config=member_config,
modules=instance.get('modules'), modules=instance.get('modules'),
locality=locality, locality=locality,
@ -128,7 +129,7 @@ class GaleraCommonCluster(cluster_models.Cluster):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, def create(cls, context, name, datastore, datastore_version,
instances, extended_properties, locality): instances, extended_properties, locality, configuration):
LOG.debug("Initiating Galera cluster creation.") LOG.debug("Initiating Galera cluster creation.")
cls._validate_cluster_instances(context, instances, datastore, cls._validate_cluster_instances(context, instances, datastore,
datastore_version) datastore_version)
@ -136,10 +137,12 @@ class GaleraCommonCluster(cluster_models.Cluster):
db_info = cluster_models.DBCluster.create( db_info = cluster_models.DBCluster.create(
name=name, tenant_id=context.tenant, name=name, tenant_id=context.tenant,
datastore_version_id=datastore_version.id, 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, 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 # Calling taskmanager to further proceed for cluster-configuration
task_api.load(context, datastore_version.manager).create_cluster( 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) db_info.update(task_status=ClusterTasks.GROWING_CLUSTER)
try: try:
locality = srv_grp.ServerGroup.convert_to_hint(self.server_group) locality = srv_grp.ServerGroup.convert_to_hint(self.server_group)
configuration_id = self.db_info.configuration_id
new_instances = self._create_instances( new_instances = self._create_instances(
context, db_info, datastore, datastore_version, instances, context, db_info, datastore, datastore_version, instances,
None, locality) None, locality, configuration_id)
task_api.load(context, datastore_version.manager).grow_cluster( task_api.load(context, datastore_version.manager).grow_cluster(
db_info.id, [instance.id for instance in new_instances]) db_info.id, [instance.id for instance in new_instances])
@ -203,6 +207,12 @@ class GaleraCommonCluster(cluster_models.Cluster):
def upgrade(self, datastore_version): def upgrade(self, datastore_version):
self.rolling_upgrade(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): class GaleraCommonClusterView(ClusterView):

View File

@ -58,7 +58,10 @@ class MongoDbCluster(models.Cluster):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, 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): consider moving into CONF and even supporting
# TODO(amcreynolds): an array of values, e.g. [3, 5, 7] # TODO(amcreynolds): an array of values, e.g. [3, 5, 7]

View File

@ -97,9 +97,12 @@ class RedisCluster(models.Cluster):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, def create(cls, context, name, datastore, datastore_version,
instances, extended_properties, locality): instances, extended_properties, locality, configuration):
LOG.debug("Initiating cluster creation.") LOG.debug("Initiating cluster creation.")
if configuration:
raise exception.ConfigurationNotSupported()
# Updating Cluster Task # Updating Cluster Task
db_info = models.DBCluster.create( db_info = models.DBCluster.create(

View File

@ -129,9 +129,12 @@ class VerticaCluster(models.Cluster):
@classmethod @classmethod
def create(cls, context, name, datastore, datastore_version, def create(cls, context, name, datastore, datastore_version,
instances, extended_properties, locality): instances, extended_properties, locality, configuration):
LOG.debug("Initiating cluster creation.") LOG.debug("Initiating cluster creation.")
if configuration:
raise exception.ConfigurationNotSupported()
vertica_conf = CONF.get(datastore_version.manager) vertica_conf = CONF.get(datastore_version.manager)
num_instances = len(instances) num_instances = len(instances)

View File

@ -209,6 +209,21 @@ class Configuration(object):
item["deleted_at"] = None item["deleted_at"] = None
DBConfigurationParameter.save(item) 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): class DBConfiguration(dbmodels.DatabaseModelBase):
_data_fields = ['name', 'description', 'tenant_id', 'datastore_version_id', _data_fields = ['name', 'description', 'tenant_id', 'datastore_version_id',
@ -256,6 +271,7 @@ class DBDatastoreConfigurationParameters(dbmodels.DatabaseModelBase):
class DatastoreConfigurationParameters(object): class DatastoreConfigurationParameters(object):
def __init__(self, db_info): def __init__(self, db_info):
self.db_info = db_info self.db_info = db_info

View File

@ -18,6 +18,7 @@ from datetime import datetime
from oslo_log import log as logging from oslo_log import log as logging
import six import six
from trove.cluster import models as cluster_models
import trove.common.apischema as apischema import trove.common.apischema as apischema
from trove.common import cfg from trove.common import cfg
from trove.common import exception from trove.common import exception
@ -198,6 +199,8 @@ class ConfigurationsController(wsgi.Controller):
deleted_at) deleted_at)
models.Configuration.save(group, items) models.Configuration.save(group, items)
self._refresh_on_all_instances(context, id) self._refresh_on_all_instances(context, id)
self._refresh_on_all_clusters(context, id)
return wsgi.Result(None, 202) return wsgi.Result(None, 202)
def edit(self, req, body, tenant_id, id): def edit(self, req, body, tenant_id, id):
@ -211,25 +214,41 @@ class ConfigurationsController(wsgi.Controller):
body['configuration']) body['configuration'])
models.Configuration.save(group, items) models.Configuration.save(group, items)
self._refresh_on_all_instances(context, id) self._refresh_on_all_instances(context, id)
self._refresh_on_all_clusters(context, id)
def _refresh_on_all_instances(self, context, configuration_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()
config = models.Configuration(context, configuration_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_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, tenant_id=context.tenant,
configuration_id=configuration_id, configuration_id=configuration_id,
deleted=False).all() deleted=False).all()
LOG.debug( for dbcluster in clusters:
"All instances with configuration group '%s' on tenant '%s': %s" LOG.debug("Re-applying configuration to cluster: %s"
% (configuration_id, context.tenant, dbinstances)) % dbcluster.id)
cluster = cluster_models.Cluster.load(context, dbcluster.id)
config = models.Configuration(context, configuration_id) cluster.configuration_attach(configuration_id)
for dbinstance in dbinstances:
LOG.debug("Applying configuration group '%s' to instance: %s"
% (configuration_id, dbinstance.id))
instance = instances_models.Instance.load(context, dbinstance.id)
instance.update_overrides(config)
def _configuration_items_list(self, group, configuration): def _configuration_items_list(self, group, configuration):
ds_version_id = group.datastore_version_id ds_version_id = group.datastore_version_id

View File

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

View File

@ -1282,11 +1282,6 @@ class Instance(BuiltInstance):
Raises exception if a configuration assign cannot Raises exception if a configuration assign cannot
currently be performed 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 # check if the instance is not ACTIVE or has tasks
status = None status = None
@ -1299,23 +1294,118 @@ class Instance(BuiltInstance):
raise exception.InvalidInstanceState(instance_id=self.id, raise exception.InvalidInstanceState(instance_id=self.id,
status=status) status=status)
def unassign_configuration(self): def attach_configuration(self, configuration_id):
LOG.debug("Unassigning the configuration from the instance %s.", LOG.debug("Attaching configuration to instance: %s", self.id)
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: if self.configuration and self.configuration.id:
LOG.debug("Unassigning the configuration id %s.", self._validate_can_perform_assign()
self.configuration.id) LOG.debug("Detaching configuration: %s", self.configuration.id)
self.remove_configuration()
else:
LOG.debug("No configuration found on instance.")
self.guest.update_overrides({}, remove=True) def remove_configuration(self):
configuration_id = self.delete_configuration()
return self.reset_configuration(configuration_id)
# Dynamically reset the configuration values back to their default def delete_configuration(self):
# values from the configuration template. """Remove configuration changes from the guest.
# Reset the values only if the default is available for all of Update Trove records if successful.
# them and restart is not required by any. This method does not update runtime values. It sets the instance task
# Mark the instance with a 'RESTART_REQUIRED' status otherwise. 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)
return configuration_id
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() flavor = self.get_flavor()
default_config = self._render_config_dict(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() current_overrides = current_config.get_configuration_overrides()
# Check the configuration template has defaults for all modified # Check the configuration template has defaults for all modified
# values. # values.
@ -1323,56 +1413,22 @@ class Instance(BuiltInstance):
for key in current_overrides.keys()) for key in current_overrides.keys())
if (not current_config.does_configuration_need_restart() and if (not current_config.does_configuration_need_restart() and
has_defaults_for_all): has_defaults_for_all):
LOG.debug("Applying runtime configuration changes.")
self.guest.apply_overrides( self.guest.apply_overrides(
{k: v for k, v in default_config.items() {k: v for k, v in default_config.items()
if k in current_overrides}) if k in current_overrides})
LOG.debug("Configuration has been applied.")
self.update_db(task_status=InstanceTasks.NONE)
return True
else: else:
LOG.debug( LOG.debug(
"Could not revert all configuration changes dynamically. " "Could not revert all configuration changes dynamically. "
"A restart will be required.") "A restart will be required.")
self.update_db(task_status=InstanceTasks.RESTART_REQUIRED)
else: else:
LOG.debug("No configuration found on instance. Skipping.") LOG.debug("There are no values to reset.")
def assign_configuration(self, configuration_id): return False
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)
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)
def _render_config_dict(self, flavor): def _render_config_dict(self, flavor):
config = template.SingleInstanceConfigTemplate( config = template.SingleInstanceConfigTemplate(

View File

@ -389,13 +389,13 @@ class InstanceController(wsgi.Controller):
configuration_id = kwargs['configuration_id'] configuration_id = kwargs['configuration_id']
with StartNotification(context, instance_id=instance.id, with StartNotification(context, instance_id=instance.id,
configuration_id=configuration_id): configuration_id=configuration_id):
instance.assign_configuration(configuration_id) instance.attach_configuration(configuration_id)
else: else:
context.notification = ( context.notification = (
notification.DBaaSInstanceDetachConfiguration(context, notification.DBaaSInstanceDetachConfiguration(context,
request=req)) request=req))
with StartNotification(context, instance_id=instance.id): with StartNotification(context, instance_id=instance.id):
instance.unassign_configuration() instance.detach_configuration()
if 'datastore_version' in kwargs: if 'datastore_version' in kwargs:
datastore_version = datastore_models.DatastoreVersion.load( datastore_version = datastore_models.DatastoreVersion.load(
instance.datastore, kwargs['datastore_version']) instance.datastore, kwargs['datastore_version'])

View File

@ -387,15 +387,6 @@ class Manager(periodic_task.PeriodicTasks):
with EndNotification(context): with EndNotification(context):
instance_tasks.upgrade(datastore_version) 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): def create_cluster(self, context, cluster_id):
with EndNotification(context, cluster_id=cluster_id): with EndNotification(context, cluster_id=cluster_id):
cluster_tasks = models.load_cluster_tasks(context, cluster_id) cluster_tasks = models.load_cluster_tasks(context, cluster_id)

View File

@ -168,6 +168,12 @@ cluster_restart_groups.extend([groups.CLUSTER_ACTIONS_RESTART_WAIT])
cluster_upgrade_groups = list(cluster_create_groups) cluster_upgrade_groups = list(cluster_create_groups)
cluster_upgrade_groups.extend([groups.CLUSTER_UPGRADE_WAIT]) 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 = list(cluster_actions_groups)
cluster_groups.extend([cluster_group.GROUP]) 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"], cluster_root_groups)
register(["cluster_root_actions"], cluster_root_actions_groups) register(["cluster_root_actions"], cluster_root_actions_groups)
register(["cluster_upgrade"], cluster_upgrade_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(["common"], common_groups)
register(["configuration"], configuration_groups) register(["configuration"], configuration_groups)
register(["configuration_create"], configuration_create_groups) register(["configuration_create"], configuration_create_groups)
@ -294,7 +302,8 @@ register(
user_actions_groups, ], user_actions_groups, ],
multi=[cluster_actions_groups, multi=[cluster_actions_groups,
cluster_negative_actions_groups, cluster_negative_actions_groups,
cluster_root_actions_groups, ] cluster_root_actions_groups,
cluster_config_actions_groups, ]
) )
register( register(

View File

@ -51,7 +51,10 @@ CFGGRP_INST_DELETE_WAIT = "scenario.cfggrp_inst_delete_wait_grp"
# Cluster Actions Group # 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 = "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_ENABLE = "scenario.cluster_actions_root_enable_grp"
CLUSTER_ACTIONS_ROOT_ACTIONS = "scenario.cluster_actions_root_actions_grp" CLUSTER_ACTIONS_ROOT_ACTIONS = "scenario.cluster_actions_root_actions_grp"
CLUSTER_ACTIONS_ROOT_GROW = "scenario.cluster_actions_root_grow_grp" CLUSTER_ACTIONS_ROOT_GROW = "scenario.cluster_actions_root_grow_grp"

View File

@ -29,7 +29,7 @@ class ClusterRunnerFactory(test_runners.RunnerFactory):
_runner_cls = 'ClusterRunner' _runner_cls = 'ClusterRunner'
@test(groups=[GROUP, groups.CLUSTER_CREATE], @test(groups=[GROUP, groups.CLUSTER_CFGGRP_CREATE],
runs_after_groups=[groups.MODULE_DELETE, runs_after_groups=[groups.MODULE_DELETE,
groups.CFGGRP_INST_DELETE, groups.CFGGRP_INST_DELETE,
groups.INST_ACTIONS_RESIZE_WAIT, groups.INST_ACTIONS_RESIZE_WAIT,
@ -39,6 +39,20 @@ class ClusterRunnerFactory(test_runners.RunnerFactory):
groups.ROOT_ACTION_INST_DELETE, groups.ROOT_ACTION_INST_DELETE,
groups.REPL_INST_DELETE_WAIT, groups.REPL_INST_DELETE_WAIT,
groups.INST_DELETE]) 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): class ClusterCreateGroup(TestGroup):
def __init__(self): def __init__(self):
@ -70,6 +84,11 @@ class ClusterCreateWaitGroup(TestGroup):
"""Wait for cluster create to complete.""" """Wait for cluster create to complete."""
self.test_runner.run_cluster_create_wait() 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]) @test(depends_on=[cluster_create_wait])
def add_initial_cluster_data(self): def add_initial_cluster_data(self):
"""Add data to cluster.""" """Add data to cluster."""
@ -178,6 +197,11 @@ class ClusterGrowWaitGroup(TestGroup):
"""Wait for cluster grow to complete.""" """Wait for cluster grow to complete."""
self.test_runner.run_cluster_grow_wait() 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]) @test(depends_on=[cluster_grow_wait])
def verify_initial_cluster_data_after_grow(self): def verify_initial_cluster_data_after_grow(self):
"""Verify the initial data still exists after cluster grow.""" """Verify the initial data still exists after cluster grow."""
@ -245,6 +269,11 @@ class ClusterUpgradeWaitGroup(TestGroup):
"""Wait for cluster upgrade to complete.""" """Wait for cluster upgrade to complete."""
self.test_runner.run_cluster_upgrade_wait() 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]) @test(depends_on=[cluster_upgrade_wait])
def verify_initial_cluster_data_after_upgrade(self): def verify_initial_cluster_data_after_upgrade(self):
"""Verify the initial data still exists after cluster upgrade.""" """Verify the initial data still exists after cluster upgrade."""
@ -298,6 +327,11 @@ class ClusterShrinkWaitGroup(TestGroup):
"""Wait for the cluster shrink to complete.""" """Wait for the cluster shrink to complete."""
self.test_runner.run_cluster_shrink_wait() 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]) @test(depends_on=[cluster_shrink_wait])
def verify_initial_cluster_data_after_shrink(self): def verify_initial_cluster_data_after_shrink(self):
"""Verify the initial data still exists after cluster shrink.""" """Verify the initial data still exists after cluster shrink."""
@ -336,6 +370,93 @@ class ClusterRootEnableShrinkGroup(TestGroup):
self.test_runner.run_verify_cluster_root_enable() self.test_runner.run_verify_cluster_root_enable()
@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, @test(groups=[GROUP, groups.CLUSTER_ACTIONS,
groups.CLUSTER_DELETE], groups.CLUSTER_DELETE],
depends_on_groups=[groups.CLUSTER_CREATE_WAIT], depends_on_groups=[groups.CLUSTER_CREATE_WAIT],
@ -345,7 +466,9 @@ class ClusterRootEnableShrinkGroup(TestGroup):
groups.CLUSTER_ACTIONS_GROW_WAIT, groups.CLUSTER_ACTIONS_GROW_WAIT,
groups.CLUSTER_ACTIONS_SHRINK_WAIT, groups.CLUSTER_ACTIONS_SHRINK_WAIT,
groups.CLUSTER_UPGRADE_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): class ClusterDeleteGroup(TestGroup):
def __init__(self): def __init__(self):
@ -376,3 +499,19 @@ class ClusterDeleteWaitGroup(TestGroup):
def cluster_delete_wait(self): def cluster_delete_wait(self):
"""Wait for the existing cluster to be gone.""" """Wait for the existing cluster to be gone."""
self.test_runner.run_cluster_delete_wait() 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()

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import os import os
from proboscis import SkipTest from proboscis import SkipTest
@ -49,6 +50,11 @@ class ClusterRunner(TestRunner):
self.initial_instance_count = None self.initial_instance_count = None
self.cluster_instances = None self.cluster_instances = None
self.cluster_removed_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 @property
def is_using_existing_cluster(self): def is_using_existing_cluster(self):
@ -62,6 +68,15 @@ class ClusterRunner(TestRunner):
def min_cluster_node_count(self): def min_cluster_node_count(self):
return 2 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', def run_cluster_create(self, num_nodes=None, expected_task_name='BUILDING',
expected_http_code=200): expected_http_code=200):
self.cluster_count_before_create = len( self.cluster_count_before_create = len(
@ -84,11 +99,11 @@ class ClusterRunner(TestRunner):
self.cluster_id = self.assert_cluster_create( self.cluster_id = self.assert_cluster_create(
self.cluster_name, instance_defs, self.locality, 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( def assert_cluster_create(
self, cluster_name, instances_def, locality, expected_task_name, self, cluster_name, instances_def, locality, configuration,
expected_http_code): expected_task_name, expected_http_code):
self.report.log("Testing cluster create: %s" % cluster_name) self.report.log("Testing cluster create: %s" % cluster_name)
@ -100,8 +115,10 @@ class ClusterRunner(TestRunner):
cluster = client.clusters.create( cluster = client.clusters.create(
cluster_name, self.instance_info.dbaas_datastore, cluster_name, self.instance_info.dbaas_datastore,
self.instance_info.dbaas_datastore_version, 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.assert_client_code(client, expected_http_code)
self.active_config_group_id = configuration
self._assert_cluster_values(cluster, expected_task_name) self._assert_cluster_values(cluster, expected_task_name)
for instance in cluster.instances: for instance in cluster.instances:
self.register_debug_inst_ids(instance['id']) self.register_debug_inst_ids(instance['id'])
@ -202,7 +219,8 @@ class ClusterRunner(TestRunner):
self.current_root_creds = client.root.create_cluster_root( self.current_root_creds = client.root.create_cluster_root(
self.cluster_id, root_credentials['password']) self.cluster_id, root_credentials['password'])
self.assert_client_code(client, expected_http_code) 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.assert_equal(root_credentials['name'],
self.current_root_creds[0]) self.current_root_creds[0])
self.assert_equal(root_credentials['password'], self.assert_equal(root_credentials['password'],
@ -506,6 +524,8 @@ class ClusterRunner(TestRunner):
check.has_field("updated", six.text_type) check.has_field("updated", six.text_type)
if check_locality: if check_locality:
check.has_field("locality", six.text_type) 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: for instance in cluster.instances:
isinstance(instance, dict) isinstance(instance, dict)
self.assert_is_not_none(instance['id']) self.assert_is_not_none(instance['id'])
@ -528,6 +548,214 @@ class ClusterRunner(TestRunner):
except exceptions.NotFound: except exceptions.NotFound:
self.assert_client_code(client, 404) 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): class CassandraClusterRunner(ClusterRunner):

View File

@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
from proboscis import SkipTest from proboscis import SkipTest
from trove.tests.config import CONFIG from trove.tests.config import CONFIG
@ -62,21 +60,9 @@ class InstanceCreateRunner(TestRunner):
self.instance_info.helper_database = instance_info.helper_database self.instance_info.helper_database = instance_info.helper_database
def run_initial_configuration_create(self, expected_http_code=200): def run_initial_configuration_create(self, expected_http_code=200):
dynamic_config = self.test_helper.get_dynamic_group() group_id, _ = self.create_initial_configuration(expected_http_code)
non_dynamic_config = self.test_helper.get_non_dynamic_group() if group_id:
values = dynamic_config or non_dynamic_config self.config_group_id = group_id
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
else: else:
raise SkipTest("No groups defined.") raise SkipTest("No groups defined.")

View File

@ -15,6 +15,7 @@
import datetime import datetime
import inspect import inspect
import json
import netaddr import netaddr
import os import os
import proboscis import proboscis
@ -903,6 +904,25 @@ class TestRunner(object):
full_list = client.databases.list(instance_id) full_list = client.databases.list(instance_id)
return {database.name: database for database in full_list} 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 CheckInstance(AttrCheck):
"""Class to check various attributes of Instance details.""" """Class to check various attributes of Instance details."""

View File

@ -53,7 +53,7 @@ class ClusterTest(trove_testtools.TestCase):
CassandraCluster._create_cluster_instances( CassandraCluster._create_cluster_instances(
self.context, 'test_cluster_id', 'test_cluster', self.context, 'test_cluster_id', 'test_cluster',
datastore, datastore_version, datastore, datastore_version,
test_instances, None, None) test_instances, None, None, None)
check_quotas.assert_called_once_with( check_quotas.assert_called_once_with(
ANY, instances=num_instances, volumes=get_vol_size.return_value) ANY, instances=num_instances, volumes=get_vol_size.return_value)

View File

@ -81,8 +81,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
[], [],
None, None None, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_unequal_flavors(self, mock_client): def test_create_unequal_flavors(self, mock_client):
@ -95,8 +94,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_unequal_volumes(self, def test_create_unequal_volumes(self,
@ -112,8 +110,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_storage_not_specified(self, def test_create_storage_not_specified(self,
@ -142,8 +139,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch('trove.cluster.models.LOG') @patch('trove.cluster.models.LOG')
def test_delete_bad_task_status(self, mock_logging): def test_delete_bad_task_status(self, mock_logging):

View File

@ -195,7 +195,7 @@ class TestClusterController(trove_testtools.TestCase):
mock_cluster_create.assert_called_with(context, 'products', mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version, datastore, datastore_version,
instances, {}, instances, {},
self.locality) self.locality, None)
@patch.object(Cluster, 'load') @patch.object(Cluster, 'load')
def test_show_cluster(self, def test_show_cluster(self,

View File

@ -159,7 +159,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id) self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products', mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version, datastore, datastore_version,
instances, {}, None) instances, {}, None, None)
@patch.object(Cluster, 'load') @patch.object(Cluster, 'load')
def test_show_cluster(self, def test_show_cluster(self,

View File

@ -196,7 +196,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id) self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products', mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version, datastore, datastore_version,
instances, {}, None) instances, {}, None, None)
@patch.object(Cluster, 'load') @patch.object(Cluster, 'load')
def test_show_cluster(self, def test_show_cluster(self,

View File

@ -159,7 +159,7 @@ class TestClusterController(trove_testtools.TestCase):
self.controller.create(req, body, tenant_id) self.controller.create(req, body, tenant_id)
mock_cluster_create.assert_called_with(context, 'products', mock_cluster_create.assert_called_with(context, 'products',
datastore, datastore_version, datastore, datastore_version,
instances, {}, None) instances, {}, None, None)
@patch.object(Cluster, 'load') @patch.object(Cluster, 'load')
def test_show_cluster(self, def test_show_cluster(self,

View File

@ -83,8 +83,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
[], {}, None [], {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_flavor_not_specified(self, mock_client): def test_create_flavor_not_specified(self, mock_client):
@ -96,8 +95,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_invalid_flavor_specified(self, def test_create_invalid_flavor_specified(self,
@ -116,8 +114,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_volume_no_specified(self, def test_create_volume_no_specified(self,
@ -132,8 +129,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
@patch.object(galera_api, 'CONF') @patch.object(galera_api, 'CONF')
@ -152,8 +148,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
@patch.object(galera_api, 'CONF') @patch.object(galera_api, 'CONF')
@ -184,8 +179,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
def test_create_volume_not_equal(self, mock_client): def test_create_volume_not_equal(self, mock_client):
@ -199,8 +193,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None instances, {}, None, None)
)
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@patch.object(inst_models.Instance, 'create') @patch.object(inst_models.Instance, 'create')
@ -219,7 +212,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None) instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id) mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)
@ -241,7 +234,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None) instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id) mock_db_create.return_value.id)
self.assertEqual(4, mock_ins_create.call_count) self.assertEqual(4, mock_ins_create.call_count)
@ -279,7 +272,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, {}, None) instances, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id) mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)

View File

@ -91,7 +91,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_w_volumes, self.instances_w_volumes,
{}, None) {}, None, None)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF') @patch.object(redis_api, 'CONF')
@ -105,7 +105,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_no_volumes, self.instances_no_volumes,
{}, None) {}, None, None)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF') @patch.object(redis_api, 'CONF')
@ -122,7 +122,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_w_volumes, self.instances_w_volumes,
{}, None) {}, None, None)
@patch.object(remote, 'create_nova_client') @patch.object(remote, 'create_nova_client')
@patch.object(redis_api, 'CONF') @patch.object(redis_api, 'CONF')
@ -151,7 +151,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_no_volumes, self.instances_no_volumes,
{}, None) {}, None, None)
@patch.object(redis_api, 'CONF') @patch.object(redis_api, 'CONF')
@patch.object(inst_models.Instance, 'create') @patch.object(inst_models.Instance, 'create')
@ -167,7 +167,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_w_volumes, {}, None) self.instances_w_volumes, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
self.dbcreate_mock.return_value.id) self.dbcreate_mock.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)
@ -199,7 +199,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
self.instances_no_volumes, {}, None) self.instances_no_volumes, {}, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
self.dbcreate_mock.return_value.id) self.dbcreate_mock.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)

View File

@ -80,7 +80,7 @@ class ClusterTest(trove_testtools.TestCase):
self.cluster_name, self.cluster_name,
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
[], None, None) [], None, None, None)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -95,8 +95,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -118,8 +117,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -137,8 +135,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -162,8 +159,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -199,8 +195,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(DBCluster, 'create') @patch.object(DBCluster, 'create')
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@ -218,8 +213,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None None, None, None)
)
@patch.object(inst_models.DBInstance, 'find_all') @patch.object(inst_models.DBInstance, 'find_all')
@patch.object(inst_models.Instance, 'create') @patch.object(inst_models.Instance, 'create')
@ -237,7 +231,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None) None, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id) mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)
@ -276,7 +270,7 @@ class ClusterTest(trove_testtools.TestCase):
self.datastore, self.datastore,
self.datastore_version, self.datastore_version,
instances, instances,
None, None) None, None, None)
mock_task_api.return_value.create_cluster.assert_called_with( mock_task_api.return_value.create_cluster.assert_called_with(
mock_db_create.return_value.id) mock_db_create.return_value.id)
self.assertEqual(3, mock_ins_create.call_count) self.assertEqual(3, mock_ins_create.call_count)

View File

@ -257,8 +257,8 @@ class TestInstanceController(trove_testtools.TestCase):
def _setup_modify_instance_mocks(self): def _setup_modify_instance_mocks(self):
instance = Mock() instance = Mock()
instance.detach_replica = Mock() instance.detach_replica = Mock()
instance.assign_configuration = Mock() instance.attach_configuration = Mock()
instance.unassign_configuration = Mock() instance.detach_configuration = Mock()
instance.update_db = Mock() instance.update_db = Mock()
return instance return instance
@ -270,8 +270,8 @@ class TestInstanceController(trove_testtools.TestCase):
instance, **args) instance, **args)
self.assertEqual(0, instance.detach_replica.call_count) self.assertEqual(0, instance.detach_replica.call_count)
self.assertEqual(0, instance.unassign_configuration.call_count) self.assertEqual(0, instance.detach_configuration.call_count)
self.assertEqual(0, instance.assign_configuration.call_count) self.assertEqual(0, instance.attach_configuration.call_count)
self.assertEqual(0, instance.update_db.call_count) self.assertEqual(0, instance.update_db.call_count)
def test_modify_instance_with_nonempty_args_calls_update_db(self): 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, self.controller._modify_instance(self.context, self.req,
instance, **args) 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): def test_modify_instance_with_None_configuration_id_arg(self):
instance = self._setup_modify_instance_mocks() instance = self._setup_modify_instance_mocks()
@ -322,7 +322,7 @@ class TestInstanceController(trove_testtools.TestCase):
self.controller._modify_instance(self.context, self.req, self.controller._modify_instance(self.context, self.req,
instance, **args) 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): def test_modify_instance_with_all_args(self):
instance = self._setup_modify_instance_mocks() instance = self._setup_modify_instance_mocks()
@ -334,5 +334,5 @@ class TestInstanceController(trove_testtools.TestCase):
instance, **args) instance, **args)
self.assertEqual(1, instance.detach_replica.call_count) 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) instance.update_db.assert_called_once_with(**args)