# Copyright (C) 2016 EMC Corporation. # 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 ddt import mock from oslo_config import cfg from oslo_utils import importutils from cinder import context from cinder import db from cinder import exception from cinder import objects from cinder.objects import fields from cinder import quota from cinder import test from cinder.tests.unit import conf_fixture from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_group from cinder.tests.unit import fake_group_snapshot from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_volume from cinder.tests.unit import utils as tests_utils from cinder.volume import api as volume_api from cinder.volume import configuration as conf from cinder.volume import driver from cinder.volume import utils as volutils GROUP_QUOTAS = quota.GROUP_QUOTAS CONF = cfg.CONF @ddt.ddt class GroupManagerTestCase(test.TestCase): def setUp(self): super(GroupManagerTestCase, self).setUp() self.volume = importutils.import_object(CONF.volume_manager) self.configuration = mock.Mock(conf.Configuration) self.context = context.get_admin_context() self.context.user_id = fake.USER_ID self.project_id = fake.PROJECT3_ID self.context.project_id = self.project_id self.volume.driver.set_initialized() self.volume.stats = {'allocated_capacity_gb': 0, 'pools': {}} self.volume_api = volume_api.API() def test_delete_volume_in_group(self): """Test deleting a volume that's tied to a group fails.""" volume_params = {'status': 'available', 'group_id': fake.GROUP_ID} volume = tests_utils.create_volume(self.context, **volume_params) self.assertRaises(exception.InvalidVolume, self.volume_api.delete, self.context, volume) @mock.patch.object(GROUP_QUOTAS, "reserve", return_value=["RESERVATION"]) @mock.patch.object(GROUP_QUOTAS, "commit") @mock.patch.object(GROUP_QUOTAS, "rollback") @mock.patch.object(driver.VolumeDriver, "delete_group", return_value=({'status': ( fields.GroupStatus.DELETED)}, [])) def test_create_delete_group(self, fake_delete_grp, fake_rollback, fake_commit, fake_reserve): """Test group can be created and deleted.""" def fake_driver_create_grp(context, group): """Make sure that the pool is part of the host.""" self.assertIn('host', group) host = group.host pool = volutils.extract_host(host, level='pool') self.assertEqual('fakepool', pool) return {'status': fields.GroupStatus.AVAILABLE} self.mock_object(self.volume.driver, 'create_group', fake_driver_create_grp) group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], host='fakehost@fakedrv#fakepool', group_type_id=fake.GROUP_TYPE_ID) group = objects.Group.get_by_id(self.context, group.id) self.assertEqual(0, len(self.notifier.notifications), self.notifier.notifications) self.volume.create_group(self.context, group) self.assertEqual(2, len(self.notifier.notifications), self.notifier.notifications) msg = self.notifier.notifications[0] self.assertEqual('group.create.start', msg['event_type']) expected = { 'status': fields.GroupStatus.AVAILABLE, 'name': 'test_group', 'availability_zone': 'nova', 'tenant_id': self.context.project_id, 'created_at': mock.ANY, 'user_id': fake.USER_ID, 'group_id': group.id, 'group_type': fake.GROUP_TYPE_ID } self.assertDictEqual(expected, msg['payload']) msg = self.notifier.notifications[1] self.assertEqual('group.create.end', msg['event_type']) self.assertDictEqual(expected, msg['payload']) self.assertEqual( group.id, objects.Group.get_by_id(context.get_admin_context(), group.id).id) self.volume.delete_group(self.context, group) grp = objects.Group.get_by_id( context.get_admin_context(read_deleted='yes'), group.id) self.assertEqual(fields.GroupStatus.DELETED, grp.status) self.assertEqual(4, len(self.notifier.notifications), self.notifier.notifications) msg = self.notifier.notifications[2] self.assertEqual('group.delete.start', msg['event_type']) self.assertDictEqual(expected, msg['payload']) msg = self.notifier.notifications[3] self.assertEqual('group.delete.end', msg['event_type']) expected['status'] = fields.GroupStatus.DELETED self.assertDictEqual(expected, msg['payload']) self.assertRaises(exception.NotFound, objects.Group.get_by_id, self.context, group.id) @mock.patch.object(GROUP_QUOTAS, "reserve", return_value=["RESERVATION"]) @mock.patch.object(GROUP_QUOTAS, "commit") @mock.patch.object(GROUP_QUOTAS, "rollback") @mock.patch.object(driver.VolumeDriver, "create_group", return_value={'status': 'available'}) @mock.patch.object(driver.VolumeDriver, "update_group") def test_update_group(self, fake_update_grp, fake_create_grp, fake_rollback, fake_commit, fake_reserve): """Test group can be updated.""" group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) self.volume.create_group(self.context, group) volume = tests_utils.create_volume( self.context, group_id=group.id, volume_type_id=fake.VOLUME_TYPE_ID, status='available', host=group.host) self.volume.create_volume(self.context, volume) volume2 = tests_utils.create_volume( self.context, group_id=None, volume_type_id=fake.VOLUME_TYPE_ID, status='available', host=group.host) self.volume.create_volume(self.context, volume) fake_update_grp.return_value = ( {'status': fields.GroupStatus.AVAILABLE}, [{'id': volume2.id, 'status': 'available'}], [{'id': volume.id, 'status': 'available'}]) self.volume.update_group(self.context, group, add_volumes=volume2.id, remove_volumes=volume.id) grp = objects.Group.get_by_id(self.context, group.id) expected = { 'status': fields.GroupStatus.AVAILABLE, 'name': 'test_group', 'availability_zone': 'nova', 'tenant_id': self.context.project_id, 'created_at': mock.ANY, 'user_id': fake.USER_ID, 'group_id': group.id, 'group_type': fake.GROUP_TYPE_ID } self.assertEqual(fields.GroupStatus.AVAILABLE, grp.status) self.assertEqual(10, len(self.notifier.notifications), self.notifier.notifications) msg = self.notifier.notifications[6] self.assertEqual('group.update.start', msg['event_type']) self.assertDictEqual(expected, msg['payload']) msg = self.notifier.notifications[8] self.assertEqual('group.update.end', msg['event_type']) self.assertDictEqual(expected, msg['payload']) grpvolumes = db.volume_get_all_by_generic_group(self.context, group.id) grpvol_ids = [grpvol['id'] for grpvol in grpvolumes] # Verify volume is removed. self.assertNotIn(volume.id, grpvol_ids) # Verify volume is added. self.assertIn(volume2.id, grpvol_ids) volume3 = tests_utils.create_volume( self.context, group_id=None, host=group.host, volume_type_id=fake.VOLUME_TYPE_ID, status='wrong-status') volume_id3 = volume3['id'] volume_get_orig = self.volume.db.volume_get self.volume.db.volume_get = mock.Mock( return_value={'status': 'wrong_status', 'id': volume_id3}) # Try to add a volume in wrong status self.assertRaises(exception.InvalidVolume, self.volume.update_group, self.context, group, add_volumes=volume_id3, remove_volumes=None) self.volume.db.volume_get.reset_mock() self.volume.db.volume_get = volume_get_orig @mock.patch('cinder.db.sqlalchemy.api.' 'volume_glance_metadata_copy_to_volume') @mock.patch('cinder.db.sqlalchemy.api.' 'volume_glance_metadata_copy_from_volume_to_volume') @mock.patch.object(driver.VolumeDriver, "create_group", return_value={'status': 'available'}) @mock.patch.object(driver.VolumeDriver, "delete_group", return_value=({'status': 'deleted'}, [])) @mock.patch.object(driver.VolumeDriver, "create_group_snapshot", return_value={'status': 'available'}) @mock.patch.object(driver.VolumeDriver, "delete_group_snapshot", return_value=({'status': 'deleted'}, [])) @mock.patch.object(driver.VolumeDriver, "create_group_from_src", return_value=(None, None)) @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.' 'create_volume_from_snapshot') @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.' 'create_cloned_volume') def test_create_group_from_src(self, mock_create_cloned_vol, mock_create_vol_from_snap, mock_create_from_src, mock_delete_grpsnap, mock_create_grpsnap, mock_delete_grp, mock_create_grp, mock_metadata_copy_volume_to_volume, mock_metadata_copy_to_volume): """Test group can be created and deleted.""" group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, status=fields.GroupStatus.AVAILABLE, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) volume = tests_utils.create_volume( self.context, group_id=group.id, status='available', multiattach=True, bootable=True, host=group.host, volume_type_id=fake.VOLUME_TYPE_ID, size=1) volume_id = volume['id'] group_snapshot_returns = self._create_group_snapshot(group.id, [volume_id]) group_snapshot = group_snapshot_returns[0] snapshot_id = group_snapshot_returns[1][0]['id'] # Create group from source group snapshot. group2 = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, group_snapshot_id=group_snapshot.id, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) group2 = objects.Group.get_by_id(self.context, group2.id) volume2 = tests_utils.create_volume( self.context, group_id=group2.id, snapshot_id=snapshot_id, status='available', host=group2.host, volume_type_id=fake.VOLUME_TYPE_ID) self.volume.create_volume(self.context, volume2) self.volume.create_group_from_src( self.context, group2, group_snapshot=group_snapshot) grp2 = objects.Group.get_by_id(self.context, group2.id) expected = { 'status': fields.GroupStatus.AVAILABLE, 'name': 'test_group', 'availability_zone': 'nova', 'tenant_id': self.context.project_id, 'created_at': mock.ANY, 'user_id': fake.USER_ID, 'group_id': group2.id, 'group_type': fake.GROUP_TYPE_ID, } self.assertEqual(fields.GroupStatus.AVAILABLE, grp2.status) self.assertEqual(group2.id, grp2['id']) self.assertEqual(group_snapshot.id, grp2['group_snapshot_id']) self.assertIsNone(grp2['source_group_id']) msg = self.notifier.notifications[2] self.assertEqual('group.create.start', msg['event_type']) self.assertDictEqual(expected, msg['payload']) msg = self.notifier.notifications[4] self.assertEqual('group.create.end', msg['event_type']) self.assertDictEqual(expected, msg['payload']) if len(self.notifier.notifications) > 6: self.assertFalse(self.notifier.notifications[6], self.notifier.notifications) self.assertEqual(6, len(self.notifier.notifications), self.notifier.notifications) self.volume.delete_group(self.context, group2) if len(self.notifier.notifications) > 9: self.assertFalse(self.notifier.notifications[10], self.notifier.notifications) self.assertEqual(9, len(self.notifier.notifications), self.notifier.notifications) msg = self.notifier.notifications[6] self.assertEqual('group.delete.start', msg['event_type']) expected['status'] = fields.GroupStatus.AVAILABLE self.assertDictEqual(expected, msg['payload']) msg = self.notifier.notifications[8] self.assertEqual('group.delete.end', msg['event_type']) expected['status'] = fields.GroupStatus.DELETED self.assertDictEqual(expected, msg['payload']) grp2 = objects.Group.get_by_id( context.get_admin_context(read_deleted='yes'), group2.id) self.assertEqual(fields.GroupStatus.DELETED, grp2.status) self.assertRaises(exception.NotFound, objects.Group.get_by_id, self.context, group2.id) # Create group from source group group3 = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, source_group_id=group.id, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) volume3 = tests_utils.create_volume( self.context, group_id=group3.id, source_volid=volume_id, status='available', host=group3.host, volume_type_id=fake.VOLUME_TYPE_ID) self.volume.create_volume(self.context, volume3) self.volume.create_group_from_src( self.context, group3, source_group=group) grp3 = objects.Group.get_by_id(self.context, group3.id) vol3 = objects.Volume.get_by_id(self.context, volume3.id) self.assertEqual(fields.GroupStatus.AVAILABLE, grp3.status) self.assertEqual(group3.id, grp3.id) self.assertEqual(group.id, grp3.source_group_id) self.assertIsNone(grp3.group_snapshot_id) self.assertEqual(volume.multiattach, vol3.multiattach) self.assertEqual(volume.bootable, vol3.bootable) self.volume.delete_group_snapshot(self.context, group_snapshot) self.volume.delete_group(self.context, group) def test_sort_snapshots(self): vol1 = {'id': fake.VOLUME_ID, 'name': 'volume 1', 'snapshot_id': fake.SNAPSHOT_ID, 'group_id': fake.GROUP_ID} vol2 = {'id': fake.VOLUME2_ID, 'name': 'volume 2', 'snapshot_id': fake.SNAPSHOT2_ID, 'group_id': fake.GROUP_ID} vol3 = {'id': fake.VOLUME3_ID, 'name': 'volume 3', 'snapshot_id': fake.SNAPSHOT3_ID, 'group_id': fake.GROUP_ID} snp1 = {'id': fake.SNAPSHOT_ID, 'name': 'snap 1', 'group_snapshot_id': fake.GROUP_ID} snp2 = {'id': fake.SNAPSHOT2_ID, 'name': 'snap 2', 'group_snapshot_id': fake.GROUP_ID} snp3 = {'id': fake.SNAPSHOT3_ID, 'name': 'snap 3', 'group_snapshot_id': fake.GROUP_ID} snp1_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp1) snp2_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp2) snp3_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp3) volumes = [] snapshots = [] volumes.append(vol1) volumes.append(vol2) volumes.append(vol3) snapshots.append(snp2_obj) snapshots.append(snp3_obj) snapshots.append(snp1_obj) i = 0 for vol in volumes: snap = snapshots[i] i += 1 self.assertNotEqual(vol['snapshot_id'], snap.id) sorted_snaps = self.volume._sort_snapshots(volumes, snapshots) i = 0 for vol in volumes: snap = sorted_snaps[i] i += 1 self.assertEqual(vol['snapshot_id'], snap.id) snapshots[2]['id'] = fake.WILL_NOT_BE_FOUND_ID self.assertRaises(exception.SnapshotNotFound, self.volume._sort_snapshots, volumes, snapshots) self.assertRaises(exception.InvalidInput, self.volume._sort_snapshots, volumes, []) def test_sort_source_vols(self): vol1 = {'id': '1', 'name': 'volume 1', 'source_volid': '1', 'group_id': '2'} vol2 = {'id': '2', 'name': 'volume 2', 'source_volid': '2', 'group_id': '2'} vol3 = {'id': '3', 'name': 'volume 3', 'source_volid': '3', 'group_id': '2'} src_vol1 = {'id': '1', 'name': 'source vol 1', 'group_id': '1'} src_vol2 = {'id': '2', 'name': 'source vol 2', 'group_id': '1'} src_vol3 = {'id': '3', 'name': 'source vol 3', 'group_id': '1'} volumes = [] src_vols = [] volumes.append(vol1) volumes.append(vol2) volumes.append(vol3) src_vols.append(src_vol2) src_vols.append(src_vol3) src_vols.append(src_vol1) i = 0 for vol in volumes: src_vol = src_vols[i] i += 1 self.assertNotEqual(vol['source_volid'], src_vol['id']) sorted_src_vols = self.volume._sort_source_vols(volumes, src_vols) i = 0 for vol in volumes: src_vol = sorted_src_vols[i] i += 1 self.assertEqual(vol['source_volid'], src_vol['id']) src_vols[2]['id'] = '9999' self.assertRaises(exception.VolumeNotFound, self.volume._sort_source_vols, volumes, src_vols) self.assertRaises(exception.InvalidInput, self.volume._sort_source_vols, volumes, []) def _create_group_snapshot(self, group_id, volume_ids, size='0'): """Create a group_snapshot object.""" grpsnap = objects.GroupSnapshot(self.context) grpsnap.user_id = fake.USER_ID grpsnap.project_id = fake.PROJECT_ID grpsnap.group_id = group_id grpsnap.status = fields.GroupStatus.CREATING grpsnap.create() # Create snapshot list for volume_id in volume_ids: snaps = [] snap = objects.Snapshot(context.get_admin_context()) snap.volume_size = size snap.user_id = fake.USER_ID snap.project_id = fake.PROJECT_ID snap.volume_id = volume_id snap.status = fields.SnapshotStatus.AVAILABLE snap.group_snapshot_id = grpsnap.id snap.create() snaps.append(snap) return grpsnap, snaps @ddt.data((CONF.host, None), (CONF.host + 'fake', 'mycluster')) @ddt.unpack @mock.patch('cinder.tests.unit.fake_notifier.FakeNotifier._notify') @mock.patch('cinder.volume.driver.VolumeDriver.create_group', autospec=True, return_value={'status': 'available'}) @mock.patch('cinder.volume.driver.VolumeDriver.delete_group', autospec=True, return_value=({'status': 'deleted'}, [])) @mock.patch('cinder.volume.driver.VolumeDriver.create_group_snapshot', autospec=True, return_value=({'status': 'available'}, [])) @mock.patch('cinder.volume.driver.VolumeDriver.delete_group_snapshot', autospec=True, return_value=({'status': 'deleted'}, [])) def test_create_delete_group_snapshot(self, host, cluster, mock_del_grpsnap, mock_create_grpsnap, mock_del_grp, _mock_create_grp, mock_notify): """Test group_snapshot can be created and deleted.""" self.volume.cluster = cluster group = tests_utils.create_group( self.context, cluster_name=cluster, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=host) volume = tests_utils.create_volume( self.context, group_id=group.id, host=group.host, cluster_name=group.cluster_name, volume_type_id=fake.VOLUME_TYPE_ID) self.volume.create_volume(self.context, volume) self.assert_notify_called(mock_notify, (['INFO', 'volume.create.start'], ['INFO', 'volume.create.end'])) group_snapshot_returns = self._create_group_snapshot(group.id, [volume.id]) group_snapshot = group_snapshot_returns[0] self.volume.create_group_snapshot(self.context, group_snapshot) self.assertEqual(group_snapshot.id, objects.GroupSnapshot.get_by_id( context.get_admin_context(), group_snapshot.id).id) self.assert_notify_called(mock_notify, (['INFO', 'volume.create.start'], ['INFO', 'volume.create.end'], ['INFO', 'group_snapshot.create.start'], ['INFO', 'snapshot.create.start'], ['INFO', 'group_snapshot.create.end'], ['INFO', 'snapshot.create.end'])) self.volume.delete_group_snapshot(self.context, group_snapshot) self.assert_notify_called(mock_notify, (['INFO', 'volume.create.start'], ['INFO', 'volume.create.end'], ['INFO', 'group_snapshot.create.start'], ['INFO', 'snapshot.create.start'], ['INFO', 'group_snapshot.create.end'], ['INFO', 'snapshot.create.end'], ['INFO', 'group_snapshot.delete.start'], ['INFO', 'snapshot.delete.start'], ['INFO', 'group_snapshot.delete.end'], ['INFO', 'snapshot.delete.end'])) grpsnap = objects.GroupSnapshot.get_by_id( context.get_admin_context(read_deleted='yes'), group_snapshot.id) self.assertEqual('deleted', grpsnap.status) self.assertRaises(exception.NotFound, objects.GroupSnapshot.get_by_id, self.context, group_snapshot.id) self.volume.delete_group(self.context, group) self.assertTrue(mock_create_grpsnap.called) self.assertTrue(mock_del_grpsnap.called) self.assertTrue(mock_del_grp.called) @mock.patch('cinder.volume.driver.VolumeDriver.create_group', return_value={'status': 'available'}) @mock.patch('cinder.volume.driver.VolumeDriver.delete_group', return_value=({'status': 'deleted'}, [])) def test_delete_group_correct_host(self, mock_del_grp, _mock_create_grp): """Test group can be deleted. Test group can be deleted when volumes are on the correct volume node. """ group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID) volume = tests_utils.create_volume( self.context, group_id=group.id, host='host1@backend1#pool1', status='creating', volume_type_id=fake.VOLUME_TYPE_ID, size=1) self.volume.host = 'host1@backend1' self.volume.create_volume(self.context, volume) self.volume.delete_group(self.context, group) grp = objects.Group.get_by_id( context.get_admin_context(read_deleted='yes'), group.id) self.assertEqual(fields.GroupStatus.DELETED, grp.status) self.assertRaises(exception.NotFound, objects.Group.get_by_id, self.context, group.id) self.assertTrue(mock_del_grp.called) @mock.patch('cinder.volume.driver.VolumeDriver.create_group', mock.Mock(return_value={'status': 'available'})) @mock.patch('cinder.volume.driver.VolumeDriver.delete_group', return_value=({'status': 'deleted'}, [])) def test_delete_group_cluster(self, mock_del_grp): """Test group can be deleted on another service in the cluster.""" cluster_name = 'cluster@backend1' self.volume.host = 'host2@backend1' self.volume.cluster = cluster_name group = tests_utils.create_group( self.context, host=CONF.host + 'fake', cluster_name=cluster_name, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID) volume = tests_utils.create_volume( self.context, group_id=group.id, host='host1@backend1#pool1', cluster_name=cluster_name, status='creating', volume_type_id=fake.VOLUME_TYPE_ID, size=1) self.volume.host = 'host2@backend1' self.volume.create_volume(self.context, volume) self.volume.delete_group(self.context, group) grp = objects.Group.get_by_id( context.get_admin_context(read_deleted='yes'), group.id) self.assertEqual(fields.GroupStatus.DELETED, grp.status) self.assertRaises(exception.NotFound, objects.Group.get_by_id, self.context, group.id) self.assertTrue(mock_del_grp.called) @mock.patch('cinder.volume.driver.VolumeDriver.create_group', return_value={'status': 'available'}) def test_delete_group_wrong_host(self, *_mock_create_grp): """Test group cannot be deleted. Test group cannot be deleted when volumes in the group are not local to the volume node. """ group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID) volume = tests_utils.create_volume( self.context, group_id=group.id, host='host1@backend1#pool1', status='creating', volume_type_id=fake.VOLUME_TYPE_ID, size=1) self.volume.host = 'host1@backend2' self.volume.create_volume(self.context, volume) self.assertRaises(exception.Invalid, self.volume.delete_group, self.context, group) grp = objects.Group.get_by_id(self.context, group.id) # Group is not deleted self.assertEqual(fields.GroupStatus.AVAILABLE, grp.status) def test_create_volume_with_group_invalid_type(self): """Test volume creation with group & invalid volume type.""" vol_type = db.volume_type_create( context.get_admin_context(), dict(name=conf_fixture.def_vol_type, extra_specs={}) ) db_vol_type = db.volume_type_get(context.get_admin_context(), vol_type.id) grp = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, status=fields.GroupStatus.AVAILABLE, volume_type_ids=[db_vol_type['id']], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) fake_type = fake_volume.fake_volume_type_obj( self.context, id=fake.VOLUME_TYPE_ID, name='fake') # Volume type must be provided when creating a volume in a # group. self.assertRaises(exception.InvalidInput, self.volume_api.create, self.context, 1, 'vol1', 'volume 1', group=grp) # Volume type must be valid. self.assertRaises(exception.InvalidInput, self.volume_api.create, self.context, 1, 'vol1', 'volume 1', volume_type=fake_type, group=grp) @mock.patch('cinder.volume.driver.VolumeDriver.create_group_snapshot', autospec=True, return_value=({'status': 'available'}, [])) def test_create_group_snapshot_with_bootable_volumes(self, mock_create_grpsnap): """Test group_snapshot can be created and deleted.""" group = tests_utils.create_group( self.context, availability_zone=CONF.storage_availability_zone, volume_type_ids=[fake.VOLUME_TYPE_ID], group_type_id=fake.GROUP_TYPE_ID, host=CONF.host) volume = tests_utils.create_volume( self.context, group_id=group.id, host=group.host, volume_type_id=fake.VOLUME_TYPE_ID) self.volume.create_volume(self.context, volume) # Create a bootable volume bootable_vol_params = {'status': 'creating', 'host': CONF.host, 'size': 1, 'bootable': True} bootable_vol = tests_utils.create_volume(self.context, group_id=group.id, **bootable_vol_params) # Create a common volume self.volume.create_volume(self.context, bootable_vol) volume_ids = [volume.id, bootable_vol.id] group_snapshot_returns = self._create_group_snapshot(group.id, volume_ids) group_snapshot = group_snapshot_returns[0] self.volume.create_group_snapshot(self.context, group_snapshot) self.assertEqual(group_snapshot.id, objects.GroupSnapshot.get_by_id( context.get_admin_context(), group_snapshot.id).id) self.assertTrue(mock_create_grpsnap.called) @mock.patch( 'cinder.tests.fake_driver.FakeLoggingVolumeDriver.create_snapshot') def test_create_group_snapshot_generic(self, mock_create_snap): grp_snp = {'id': fake.GROUP_SNAPSHOT_ID, 'group_id': fake.GROUP_ID, 'name': 'group snap 1'} snp1 = {'id': fake.SNAPSHOT_ID, 'name': 'snap 1', 'group_snapshot_id': fake.GROUP_SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID} snp2 = {'id': fake.SNAPSHOT2_ID, 'name': 'snap 2', 'group_snapshot_id': fake.GROUP_SNAPSHOT_ID, 'volume_id': fake.VOLUME2_ID} snp1_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp1) snp2_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp2) snapshots = [] snapshots.append(snp1_obj) snapshots.append(snp2_obj) driver_update = {'test_snap_key': 'test_val'} mock_create_snap.return_value = driver_update model_update, snapshot_model_updates = ( self.volume._create_group_snapshot_generic( self.context, grp_snp, snapshots)) for update in snapshot_model_updates: self.assertEqual(driver_update['test_snap_key'], update['test_snap_key']) @mock.patch( 'cinder.tests.fake_driver.FakeLoggingVolumeDriver.' 'create_volume_from_snapshot') @mock.patch( 'cinder.tests.fake_driver.FakeLoggingVolumeDriver.' 'create_cloned_volume') def test_create_group_from_src_generic(self, mock_create_clone, mock_create_vol_from_snap): grp = {'id': fake.GROUP_ID, 'name': 'group 1'} grp_snp = {'id': fake.GROUP_SNAPSHOT_ID, 'group_id': fake.GROUP_ID, 'name': 'group snap 1'} grp2 = {'id': fake.GROUP2_ID, 'name': 'group 2', 'group_snapshot_id': fake.GROUP_SNAPSHOT_ID} vol1 = {'id': fake.VOLUME_ID, 'name': 'volume 1', 'group_id': fake.GROUP_ID} vol2 = {'id': fake.VOLUME2_ID, 'name': 'volume 2', 'group_id': fake.GROUP_ID} snp1 = {'id': fake.SNAPSHOT_ID, 'name': 'snap 1', 'group_snapshot_id': fake.GROUP_SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID} snp2 = {'id': fake.SNAPSHOT2_ID, 'name': 'snap 2', 'group_snapshot_id': fake.GROUP_SNAPSHOT_ID, 'volume_id': fake.VOLUME2_ID} snp1_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp1) snp2_obj = fake_snapshot.fake_snapshot_obj(self.context, **snp2) snapshots = [] snapshots.append(snp1_obj) snapshots.append(snp2_obj) vol3 = {'id': fake.VOLUME3_ID, 'name': 'volume 3', 'snapshot_id': fake.SNAPSHOT_ID, 'group_id': fake.GROUP2_ID} vol4 = {'id': fake.VOLUME4_ID, 'name': 'volume 4', 'snapshot_id': fake.SNAPSHOT2_ID, 'group_id': fake.GROUP2_ID} vol3_obj = fake_volume.fake_volume_obj(self.context, **vol3) vol4_obj = fake_volume.fake_volume_obj(self.context, **vol4) vols2 = [] vols2.append(vol3_obj) vols2.append(vol4_obj) grp2_obj = fake_group.fake_group_obj(self.context, **grp2) grp_snp_obj = fake_group_snapshot.fake_group_snapshot_obj( self.context, **grp_snp) driver_update = {'test_key': 'test_val'} mock_create_vol_from_snap.return_value = driver_update model_update, vol_model_updates = ( self.volume._create_group_from_src_generic( self.context, grp2_obj, vols2, grp_snp_obj, snapshots)) for update in vol_model_updates: self.assertEqual(driver_update['test_key'], update['test_key']) vol1_obj = fake_volume.fake_volume_obj(self.context, **vol1) vol2_obj = fake_volume.fake_volume_obj(self.context, **vol2) vols = [] vols.append(vol1_obj) vols.append(vol2_obj) grp_obj = fake_group.fake_group_obj(self.context, **grp) grp3 = {'id': fake.GROUP3_ID, 'name': 'group 3', 'source_group_id': fake.GROUP_ID} grp3_obj = fake_group.fake_group_obj(self.context, **grp3) vol5 = {'id': fake.VOLUME5_ID, 'name': 'volume 5', 'source_volid': fake.VOLUME_ID, 'group_id': fake.GROUP3_ID} vol6 = {'id': fake.VOLUME6_ID, 'name': 'volume 6', 'source_volid': fake.VOLUME2_ID, 'group_id': fake.GROUP3_ID} vol5_obj = fake_volume.fake_volume_obj(self.context, **vol5) vol6_obj = fake_volume.fake_volume_obj(self.context, **vol6) vols3 = [] vols3.append(vol5_obj) vols3.append(vol6_obj) driver_update = {'test_key2': 'test_val2'} mock_create_clone.return_value = driver_update model_update, vol_model_updates = ( self.volume._create_group_from_src_generic( self.context, grp3_obj, vols3, None, None, grp_obj, vols)) for update in vol_model_updates: self.assertEqual(driver_update['test_key2'], update['test_key2'])