51f2655770
In order to allow clusters to be all on the same hypervisor (affinity) or all on different hypervisors (anti-affinity) a new argument (locality) needed to be added to the Trove cluster-create API. This changeset addresses the Trove server part of this feature. 'locality' can now be added to the ReST payload for a cluster-create command and it is passed along as a scheduler hint to Nova. The cluster scenario tests were enhanced to test that 'affinity' works. Testing that 'anti-affinity' fails proved to be too time consuming, since at present the only way for a cluster to 'fail' is to timeout (and that takes 10 hours). The server-group is checked to make sure it is created properly, and that it has been deleted after all the related instances are gone. DocImpact: New functionality Partially implements: blueprint replication-cluster-locality Depends-On: Ie46cfa69ffe6a64760aca38c495563f5724bd0d0 Change-Id: I657bf5c023d0257e462cc39f57c16eb6ee83807a
224 lines
10 KiB
Python
224 lines
10 KiB
Python
# Copyright 2015 Tesora Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import mock
|
|
import uuid
|
|
|
|
from trove.cluster import models
|
|
from trove.cluster import tasks
|
|
from trove.common import cfg
|
|
from trove.common import exception
|
|
from trove.common import remote
|
|
from trove.common.strategies.cluster.experimental.mongodb import api
|
|
from trove.instance import models as inst_models
|
|
from trove.instance import tasks as inst_tasks
|
|
from trove.tests.unittests import trove_testtools
|
|
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class MongoDBClusterTest(trove_testtools.TestCase):
|
|
def setUp(self):
|
|
super(MongoDBClusterTest, self).setUp()
|
|
self.cluster_id = str(uuid.uuid4())
|
|
self.cluster_name = "Cluster" + self.cluster_id
|
|
self.tenant_id = "23423432"
|
|
self.dv_id = "1"
|
|
self.db_info = models.DBCluster(models.ClusterTasks.NONE,
|
|
id=self.cluster_id,
|
|
name=self.cluster_name,
|
|
tenant_id=self.tenant_id,
|
|
datastore_version_id=self.dv_id,
|
|
task_id=models.ClusterTasks.NONE._code)
|
|
self.context = mock.Mock()
|
|
self.datastore = mock.Mock()
|
|
self.dv = mock.Mock()
|
|
self.datastore_version = self.dv
|
|
self.cluster = api.MongoDbCluster(self.context, self.db_info,
|
|
self.datastore,
|
|
self.datastore_version)
|
|
self.cluster._server_group_loaded = True
|
|
self.manager = mock.Mock()
|
|
self.cluster.manager = self.manager
|
|
self.volume_support = CONF.get('mongodb').volume_support
|
|
self.remote_nova = remote.create_nova_client
|
|
|
|
def tearDown(self):
|
|
super(MongoDBClusterTest, self).tearDown()
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_instances')
|
|
@mock.patch.object(api.MongoDbCluster, '_create_shard_instances',
|
|
return_value=['id1', 'id2', 'id3'])
|
|
@mock.patch.object(api.MongoDbCluster, '_create_query_router_instances',
|
|
return_value=['id4'])
|
|
@mock.patch.object(api.MongoDbCluster, 'update_db')
|
|
def test_grow(self, mock_update_db,
|
|
mock_create_query_router_instances,
|
|
mock_create_shard_instances,
|
|
mock_check_instances, mock_check_quotas, mock_prep_resize):
|
|
instance1 = {'name': 'replicaA', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
instance2 = {'name': 'replicaB', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaA'}
|
|
instance3 = {'name': 'replicaC', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaA'}
|
|
instance4 = {'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'query_router'}
|
|
|
|
self.cluster.grow([instance1, instance2, instance3, instance4])
|
|
|
|
self.assertEqual(mock_prep_resize.called, True)
|
|
mock_create_shard_instances.assert_called_with([instance1, instance2,
|
|
instance3], None)
|
|
mock_create_query_router_instances.assert_called_with([instance4],
|
|
None)
|
|
mock_update_db.assert_called_with(
|
|
task_status=tasks.ClusterTasks.GROWING_CLUSTER
|
|
)
|
|
self.manager.grow_cluster.assert_called_with(
|
|
self.cluster_id, ['id1', 'id2', 'id3', 'id4']
|
|
)
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
def test_grow_invalid_type(self, mock_check_quotas, mock_prep_resize):
|
|
instance1 = {'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'config_server'}
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.grow,
|
|
[instance1])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
def test_grow_invalid_shard_size(self, mock_check_quotas,
|
|
mock_prep_resize):
|
|
instance1 = {'name': 'replicaA', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaB'}
|
|
instance2 = {'name': 'replicaB', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaA'}
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.grow,
|
|
[instance1, instance2])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
def test_grow_no_name(self, mock_check_quotas, mock_prep_resize):
|
|
instance1 = {'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.grow,
|
|
[instance1])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
def test_grow_repeated_name(self, mock_check_quotas, mock_prep_resize):
|
|
instance1 = {'name': 'replicaA', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
instance1 = {'name': 'replicaA', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
instance1 = {'name': 'replicaC', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.grow,
|
|
[instance1])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_quotas')
|
|
def test_grow_bad_relations(self, mock_check_quotas, mock_prep_resize):
|
|
instance1 = {'name': 'replicaA', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaB'}
|
|
instance2 = {'name': 'replicaB', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaC'}
|
|
instance3 = {'name': 'replicaC', 'flavor_id': 1, 'volume_size': 5,
|
|
'instance_type': 'replica', 'related_to': 'replicaD'}
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.grow,
|
|
[instance1, instance2, instance3])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
@mock.patch.object(api.MongoDbCluster, '_check_shard_status')
|
|
@mock.patch.object(api.MongoDbCluster, 'update_db')
|
|
@mock.patch.object(inst_models, 'load_any_instance')
|
|
def test_shrink(self, mock_load_any_instance, mock_update_db,
|
|
mock_check_shard_status, mock_prep_resize):
|
|
self._mock_db_instances()
|
|
self.cluster.query_routers.append(
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id6',
|
|
cluster_id=self.cluster_id,
|
|
type='query_router')
|
|
)
|
|
|
|
self.cluster.shrink(['id1', 'id2', 'id3', 'id4'])
|
|
|
|
self.assertEqual(mock_prep_resize.called, True)
|
|
mock_check_shard_status.assert_called_with('id1')
|
|
mock_update_db.assert_called_with(
|
|
task_status=tasks.ClusterTasks.SHRINKING_CLUSTER
|
|
)
|
|
self.assertEqual(4, mock_load_any_instance().delete.call_count)
|
|
self.manager.shrink_cluster.assert_called_with(
|
|
self.cluster_id, ['id1', 'id2', 'id3', 'id4']
|
|
)
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
def test_shrink_invalid_type(self, mock_prep_resize):
|
|
self._mock_db_instances()
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.shrink,
|
|
['id5'])
|
|
|
|
@mock.patch.object(api.MongoDbCluster, '_prep_resize')
|
|
def test_shrink_incomplete_shard(self, mock_prep_resize):
|
|
self._mock_db_instances()
|
|
self.assertRaises(exception.TroveError,
|
|
self.cluster.shrink,
|
|
['id1', 'id2'])
|
|
|
|
def _mock_db_instances(self):
|
|
self.shard_id = uuid.uuid4()
|
|
self.cluster.members = [
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id1',
|
|
cluster_id=self.cluster_id,
|
|
shard_id=self.shard_id,
|
|
type='member'),
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id2',
|
|
cluster_id=self.cluster_id,
|
|
shard_id=self.shard_id,
|
|
type='member'),
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id3',
|
|
cluster_id=self.cluster_id,
|
|
shard_id=self.shard_id,
|
|
type='member'),
|
|
]
|
|
self.cluster.query_routers = [
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id4',
|
|
cluster_id=self.cluster_id,
|
|
type='query_router')
|
|
]
|
|
self.cluster.config_svrs = [
|
|
inst_models.DBInstance(inst_tasks.InstanceTasks.NONE,
|
|
id='id5',
|
|
cluster_id=self.cluster_id,
|
|
type='config_server')
|
|
]
|