# 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. from unittest import mock import uuid from novaclient import exceptions as nova_exceptions from trove.cluster import models from trove.cluster import tasks from trove.common import cfg from trove.common import clients from trove.common import exception 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.quota.quota import QUOTAS from trove.taskmanager import api as task_api from trove.tests.unittests import trove_testtools CONF = cfg.CONF class FakeOptGroup(object): def __init__(self, num_config_servers_per_cluster=3, num_query_routers_per_cluster=1, config_servers_volume_size=10, query_routers_volume_size=10, cluster_secure=True, volume_support=True, device_path='/dev/vdb'): self.num_config_servers_per_cluster = num_config_servers_per_cluster self.num_query_routers_per_cluster = num_query_routers_per_cluster self.config_servers_volume_size = config_servers_volume_size self.query_routers_volume_size = query_routers_volume_size self.cluster_secure = cluster_secure self.volume_support = volume_support self.device_path = device_path 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.dv.manager = "mongodb" 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 = clients.create_nova_client self.instances = [ {'volume_size': 1, 'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}, {'volume_size': 1, 'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}, {'volume_size': 1, 'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}] def tearDown(self): super(MongoDBClusterTest, self).tearDown() def test_create_configuration_specified(self): configuration = "foo-config" self.assertRaises(exception.ConfigurationNotSupported, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, {}, None, configuration) def test_create_invalid_instance_numbers_specified(self): instance = [ {'volume_size': 1, 'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}]} ] self.assertRaises(exception.ClusterNumInstancesNotSupported, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instance, {}, None, None) @mock.patch.object(clients, 'create_nova_client') def test_create_invalid_flavor_specified(self, mock_client): (mock_client.return_value.flavors.get) = mock.Mock( side_effect=nova_exceptions.NotFound( 404, "Flavor id not found.")) self.assertRaises(exception.FlavorNotFound, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, {}, None, None) @mock.patch.object(clients, 'create_nova_client') def test_create_flavor_not_equal(self, mock_client): instances = self.instances instances[0]['flavor_id'] = '4321' flavors = mock.Mock() mock_client.return_value.flavors = flavors self.assertRaises(exception.ClusterFlavorsNotEqual, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) @mock.patch.object(clients, 'create_nova_client') def test_create_volume_not_equal(self, mock_client): instances = self.instances instances[0]['volume_size'] = 2 flavors = mock.Mock() mock_client.return_value.flavors = flavors self.assertRaises(exception.ClusterVolumeSizesNotEqual, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) @mock.patch.object(clients, 'create_nova_client') def test_create_volume_not_specified(self, mock_client): instances = [ {'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}, {'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}, {'flavor_id': '1234', 'nics': [{"net-id": "foo-bar"}], 'region_name': "foo-region"}] flavors = mock.Mock() mock_client.return_value.flavors = flavors self.assertRaises(exception.ClusterVolumeSizeRequired, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(api, 'CONF') def test_create_storage_specified_with_no_volume_support(self, mock_conf, mock_client): mock_conf.get = mock.Mock( return_value=FakeOptGroup(volume_support=False)) flavors = mock.Mock() mock_client.return_value.flavors = flavors self.assertRaises(exception.VolumeNotSupported, models.Cluster.create, mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, {}, None, None) @mock.patch.object(task_api, 'load') @mock.patch.object(inst_models.Instance, 'create') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(clients, 'create_neutron_client') @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(api, 'check_quotas') def test_create_validate_volumes_deltas(self, mock_check_quotas, *args): extended_properties = { "configsvr_volume_size": 5, "mongos_volume_size": 7} self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, extended_properties, None, None) deltas = {'instances': 7, 'volumes': 25} # volumes=1*3+5*3+7*1 mock_check_quotas.assert_called_with(mock.ANY, deltas) @mock.patch.object(task_api, 'load') @mock.patch.object(inst_models.Instance, 'create') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(clients, 'create_neutron_client') def test_create(self, mock_neutron_client, mock_nova_client, mock_check_quotas, mock_db_create, mock_ins_create, mock_task_api): instances = self.instances flavors = mock.Mock() networks = mock.Mock() mock_neutron_client.return_value.find_resource = networks mock_nova_client.return_value.flavors = flavors self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) mock_task_api.return_value.create_cluster.assert_called_with( mock_db_create.return_value.id) self.assertEqual(7, mock_ins_create.call_count) @mock.patch.object(task_api, 'load') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(models, 'validate_instance_nics') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(models, 'validate_instance_flavors') @mock.patch.object(inst_models.Instance, 'create') def test_create_with_correct_nics(self, mock_ins_create, *args): self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, {}, None, None) nics = [{"net-id": "foo-bar"}] nics_count = [kw.get('nics') for _, kw in mock_ins_create.call_args_list].count(nics) self.assertEqual(7, nics_count) @mock.patch.object(task_api, 'load') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(models, 'validate_instance_nics') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(models, 'validate_instance_flavors') @mock.patch.object(inst_models.Instance, 'create') def test_create_with_extended_properties(self, mock_ins_create, *args): extended_properties = { "num_configsvr": 5, "num_mongos": 7, "configsvr_volume_size": 8, "configsvr_volume_type": "foo_type", "mongos_volume_size": 9, "mongos_volume_type": "bar_type"} self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, self.instances, extended_properties, None, None) volume_args_list = [ (arg[8], kw['volume_type']) for arg, kw in mock_ins_create.call_args_list ] self.assertEqual(5, volume_args_list.count((8, "foo_type"))) self.assertEqual(7, volume_args_list.count((9, "bar_type"))) @mock.patch.object(task_api, 'load') @mock.patch.object(inst_models.Instance, 'create') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(clients, 'create_neutron_client') @mock.patch.object(api, 'CONF') def test_create_with_lower_configsvr(self, mock_conf, mock_neutron_client, mock_nova_client, ock_check_quotas, mock_db_create, mock_ins_create, mock_task_api): mock_conf.get = mock.Mock( return_value=FakeOptGroup(num_config_servers_per_cluster=1)) instances = self.instances flavors = mock.Mock() networks = mock.Mock() mock_nova_client.return_value.flavors = flavors mock_neutron_client.return_value.find_resource = networks self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) mock_task_api.return_value.create_cluster.assert_called_with( mock_db_create.return_value.id) self.assertEqual(5, mock_ins_create.call_count) @mock.patch.object(task_api, 'load') @mock.patch.object(inst_models.Instance, 'create') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(clients, 'create_neutron_client') @mock.patch.object(api, 'CONF') def test_create_with_higher_configsvr(self, mock_conf, mock_neutron_client, mock_nova_client, mock_check_quotas, mock_db_create, mock_ins_create, mock_task_api): mock_conf.get = mock.Mock( return_value=FakeOptGroup(num_config_servers_per_cluster=5)) instances = self.instances flavors = mock.Mock() networks = mock.Mock() mock_nova_client.return_value.flavors = flavors mock_neutron_client.return_value.find_resource = networks self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) mock_task_api.return_value.create_cluster.assert_called_with( mock_db_create.return_value.id) self.assertEqual(9, mock_ins_create.call_count) @mock.patch.object(task_api, 'load') @mock.patch.object(inst_models.Instance, 'create') @mock.patch.object(models.DBCluster, 'create') @mock.patch.object(QUOTAS, 'check_quotas') @mock.patch.object(clients, 'create_nova_client') @mock.patch.object(clients, 'create_neutron_client') @mock.patch.object(api, 'CONF') def test_create_with_higher_mongos(self, mock_conf, mock_neutron_client, mock_nova_client, mock_check_quotas, mock_db_create, mock_ins_create, mock_task_api): mock_conf.get = mock.Mock( return_value=FakeOptGroup(num_query_routers_per_cluster=4)) instances = self.instances flavors = mock.Mock() networks = mock.Mock() mock_nova_client.return_value.flavors = flavors mock_neutron_client.return_value.find_resource = networks self.cluster.create(mock.Mock(), self.cluster_name, self.datastore, self.datastore_version, instances, {}, None, None) mock_task_api.return_value.create_cluster.assert_called_with( mock_db_create.return_value.id) self.assertEqual(10, mock_ins_create.call_count) @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.assertTrue(mock_prep_resize.called) 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.assertTrue(mock_prep_resize.called) 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') ]