from cinder import context
from cinder import exception
from cinder import test
+from cinder.tests.unit import fake_consistencygroup
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit import utils
def test_create_consistencygroup_from_cgsnapshot(self):
output_migrate_verify = ('The specified source LUN '
'is not currently migrating.', 23)
- new_cg = self.testData.test_cg.copy()
- new_cg.update(
- {'id': 'new_cg_id'})
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ new_cg.id = 'new_cg_id'
vol1_in_new_cg = self.testData.test_volume_cg.copy()
vol1_in_new_cg.update(
{'name': 'vol1_in_cg',
src_cgsnap = self.testData.test_cgsnapshot
snap1_in_src_cgsnap = self.testData.test_member_cgsnapshot.copy()
snap1_in_src_cgsnap.update(
- {'volume': self.testData.test_volume,
- 'volume_name': 'src_vol1'})
+ {'volume': fake_volume.fake_volume_obj(
+ None, **self.testData.test_volume),
+ 'expected_attrs': ['volume']})
+ snap1_in_src_cgsnap = fake_snapshot.fake_snapshot_obj(
+ None, **snap1_in_src_cgsnap)
snap2_in_src_cgsnap = self.testData.test_member_cgsnapshot.copy()
snap2_in_src_cgsnap.update(
- {'volume': self.testData.test_volume2,
- 'volume_name': 'src_vol2'})
+ {'volume': fake_volume.fake_volume_obj(
+ None, **self.testData.test_volume2),
+ 'expected_attrs': ['volume']})
+ snap2_in_src_cgsnap = fake_snapshot.fake_snapshot_obj(
+ None, **snap2_in_src_cgsnap)
copied_snap_name = 'temp_snapshot_for_%s' % new_cg['id']
td = self.testData
commands = [td.SNAP_COPY_CMD(src_cgsnap['id'], copied_snap_name),
td.ALLOW_READWRITE_ON_SNAP_CMD(copied_snap_name),
td.SNAP_MP_CREATE_CMD(vol1_in_new_cg['name'],
- snap1_in_src_cgsnap['volume_name']),
+ snap1_in_src_cgsnap.volume_name),
td.SNAP_ATTACH_CMD(vol1_in_new_cg['name'],
copied_snap_name),
td.LUN_CREATION_CMD(vol1_in_new_cg['name'] + '_dest',
td.MIGRATION_CMD(6231, 1),
td.SNAP_MP_CREATE_CMD(vol2_in_new_cg['name'],
- snap2_in_src_cgsnap['volume_name']),
+ snap2_in_src_cgsnap.volume_name),
td.SNAP_ATTACH_CMD(vol2_in_new_cg['name'],
copied_snap_name),
td.LUN_CREATION_CMD(vol2_in_new_cg['name'] + '_dest',
mock.call(*td.SNAP_COPY_CMD(src_cgsnap['id'], copied_snap_name)),
mock.call(*td.ALLOW_READWRITE_ON_SNAP_CMD(copied_snap_name)),
mock.call(*td.SNAP_MP_CREATE_CMD(vol1_in_new_cg['name'],
- snap1_in_src_cgsnap['volume_name']),
+ snap1_in_src_cgsnap.volume_name),
poll=False),
mock.call(*td.LUN_PROPERTY_ALL_CMD(vol1_in_new_cg['name']),
poll=True),
mock.call(*td.MIGRATION_CMD(6231, 1),
poll=True, retry_disable=True),
mock.call(*td.SNAP_MP_CREATE_CMD(vol2_in_new_cg['name'],
- snap2_in_src_cgsnap['volume_name']),
+ snap2_in_src_cgsnap.volume_name),
poll=False),
mock.call(*td.LUN_PROPERTY_ALL_CMD(vol2_in_new_cg['name']),
poll=True),
mock.call(*td.DELETE_CG_SNAPSHOT(copied_snap_name))]
self.assertEqual(expect_cmd, fake_cli.call_args_list)
- def test_create_consistencygroup_from_othersource(self):
- new_cg = self.testData.test_cg.copy()
- new_cg.update(
- {'id': 'new_cg_id'})
- vol1_in_new_cg = self.testData.test_volume_cg.copy()
- vol1_in_new_cg.update(
- {'name': 'vol1_in_cg',
- 'id': '111111',
- 'consistencygroup_id': 'new_cg_id',
- 'provider_location': None})
- vol2_in_new_cg = self.testData.test_volume_cg.copy()
- vol2_in_new_cg.update(
- {'name': 'vol2_in_cg',
- 'id': '222222',
- 'consistencygroup_id': 'new_cg_id',
- 'provider_location': None})
+ def test_create_cg_from_src_failed_without_source(self):
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ vol1_in_new_cg = self.testData.test_volume_cg
self.driverSetup()
self.assertRaises(
exception.InvalidInput,
self.driver.create_consistencygroup_from_src,
- new_cg, [vol1_in_new_cg, vol2_in_new_cg],
+ new_cg, [vol1_in_new_cg],
None, None, None, None)
+ def test_create_cg_from_src_failed_with_multiple_sources(self):
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ vol1_in_new_cg = self.testData.test_volume_cg
+ src_cgsnap = self.testData.test_cgsnapshot
+ snap1_in_src_cgsnap = fake_snapshot.fake_snapshot_obj(
+ None, **self.testData.test_member_cgsnapshot)
+ src_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ src_cg.id = 'fake_source_cg'
+ vol1_in_src_cg = {'id': 'fake_volume',
+ 'consistencygroup_id': src_cg.id}
+ self.driverSetup()
+ self.assertRaises(
+ exception.InvalidInput,
+ self.driver.create_consistencygroup_from_src,
+ new_cg, [vol1_in_new_cg],
+ src_cgsnap, [snap1_in_src_cgsnap], src_cg, [vol1_in_src_cg])
+
+ def test_create_cg_from_src_failed_with_invalid_source(self):
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ src_cgsnap = self.testData.test_cgsnapshot
+ vol1_in_new_cg = self.testData.test_volume_cg
+
+ src_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ src_cg.id = 'fake_source_cg'
+ self.driverSetup()
+ self.assertRaises(
+ exception.InvalidInput,
+ self.driver.create_consistencygroup_from_src,
+ new_cg, [vol1_in_new_cg],
+ src_cgsnap, None, src_cg, None)
+
def test_create_cg_from_cgsnapshot_migrate_failed(self):
- new_cg = self.testData.test_cg.copy()
- new_cg.update(
- {'id': 'new_cg_id'})
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ new_cg.id = 'new_cg_id'
vol1_in_new_cg = self.testData.test_volume_cg.copy()
vol1_in_new_cg.update(
{'name': 'vol1_in_cg',
src_cgsnap = self.testData.test_cgsnapshot
snap1_in_src_cgsnap = self.testData.test_member_cgsnapshot.copy()
snap1_in_src_cgsnap.update(
- {'volume': self.testData.test_volume,
- 'volume_name': 'src_vol1'})
+ {'volume': fake_volume.fake_volume_obj(
+ None, **self.testData.test_volume),
+ 'expected_attrs': ['volume']})
+ snap1_in_src_cgsnap = fake_snapshot.fake_snapshot_obj(
+ None, **snap1_in_src_cgsnap)
snap2_in_src_cgsnap = self.testData.test_member_cgsnapshot.copy()
snap2_in_src_cgsnap.update(
- {'volume': self.testData.test_volume2,
- 'volume_name': 'src_vol2'})
+ {'volume': fake_volume.fake_volume_obj(
+ None, **self.testData.test_volume2),
+ 'expected_attrs': ['volume']})
+ snap2_in_src_cgsnap = fake_snapshot.fake_snapshot_obj(
+ None, **snap2_in_src_cgsnap)
copied_snap_name = 'temp_snapshot_for_%s' % new_cg['id']
td = self.testData
commands = [td.LUN_PROPERTY_ALL_CMD(vol1_in_new_cg['name'] + '_dest'),
mock.call(*td.SNAP_DELETE_CMD(copied_snap_name), poll=True)]
fake_cli.assert_has_calls(expect_cmd)
+ def test_create_consistencygroup_from_cg(self):
+ output_migrate_verify = ('The specified source LUN '
+ 'is not currently migrating.', 23)
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ new_cg.id = 'new_cg_id'
+ vol1_in_new_cg = self.testData.test_volume_cg.copy()
+ vol1_in_new_cg.update(
+ {'name': 'vol1_in_cg',
+ 'id': '111111',
+ 'consistencygroup_id': 'new_cg_id',
+ 'provider_location': None})
+ vol2_in_new_cg = self.testData.test_volume_cg.copy()
+ vol2_in_new_cg.update(
+ {'name': 'vol2_in_cg',
+ 'id': '222222',
+ 'consistencygroup_id': 'new_cg_id',
+ 'provider_location': None})
+ src_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ src_cg.id = 'src_cg_id'
+ vol1_in_src_cg = self.testData.test_volume_cg.copy()
+ vol1_in_src_cg.update(
+ {'name': 'vol1_in_src_cg',
+ 'id': '111110000',
+ 'consistencygroup_id': 'src_cg_id',
+ 'provider_location': None})
+ vol2_in_src_cg = self.testData.test_volume_cg.copy()
+ vol2_in_src_cg.update(
+ {'name': 'vol2_in_src_cg',
+ 'id': '222220000',
+ 'consistencygroup_id': 'src_cg_id',
+ 'provider_location': None})
+ temp_snap_name = 'temp_snapshot_for_%s' % new_cg['id']
+ td = self.testData
+ commands = [td.CREATE_CG_SNAPSHOT(src_cg['id'], temp_snap_name),
+ td.SNAP_MP_CREATE_CMD(vol1_in_new_cg['name'],
+ vol1_in_src_cg['name']),
+ td.SNAP_ATTACH_CMD(vol1_in_new_cg['name'],
+ temp_snap_name),
+ td.LUN_CREATION_CMD(vol1_in_new_cg['name'] + '_dest',
+ vol1_in_new_cg['size'],
+ 'unit_test_pool', 'thin', None),
+ td.LUN_PROPERTY_ALL_CMD(vol1_in_new_cg['name'] + '_dest'),
+ td.LUN_PROPERTY_ALL_CMD(vol1_in_new_cg['name']),
+ td.MIGRATION_CMD(6231, 1),
+
+ td.SNAP_MP_CREATE_CMD(vol2_in_new_cg['name'],
+ vol2_in_src_cg['name']),
+ td.SNAP_ATTACH_CMD(vol2_in_new_cg['name'],
+ temp_snap_name),
+ td.LUN_CREATION_CMD(vol2_in_new_cg['name'] + '_dest',
+ vol2_in_new_cg['size'],
+ 'unit_test_pool', 'thin', None),
+ td.LUN_PROPERTY_ALL_CMD(vol2_in_new_cg['name'] + '_dest'),
+ td.LUN_PROPERTY_ALL_CMD(vol2_in_new_cg['name']),
+ td.MIGRATION_CMD(6232, 2),
+
+ td.MIGRATION_VERIFY_CMD(6231),
+ td.MIGRATION_VERIFY_CMD(6232),
+ td.CREATE_CONSISTENCYGROUP_CMD(new_cg['id'], [6231, 6232]),
+ td.DELETE_CG_SNAPSHOT(temp_snap_name)
+ ]
+ results = [SUCCEED, SUCCEED, SUCCEED, SUCCEED,
+ td.LUN_PROPERTY(vol1_in_new_cg['name'] + '_dest',
+ lunid=1),
+ td.LUN_PROPERTY(vol1_in_new_cg['name'], lunid=6231),
+ SUCCEED, SUCCEED, SUCCEED, SUCCEED,
+ td.LUN_PROPERTY(vol2_in_new_cg['name'] + '_dest',
+ lunid=2),
+ td.LUN_PROPERTY(vol2_in_new_cg['name'], lunid=6232),
+ SUCCEED, output_migrate_verify, output_migrate_verify,
+ SUCCEED, SUCCEED]
+
+ fake_cli = self.driverSetup(commands, results)
+
+ cg_model_update, volumes_model_update = (
+ self.driver.create_consistencygroup_from_src(
+ None, new_cg, [vol1_in_new_cg, vol2_in_new_cg],
+ cgsnapshot=None, snapshots=None,
+ source_cg=src_cg, source_vols=[vol1_in_src_cg,
+ vol2_in_src_cg]))
+ self.assertEqual(2, len(volumes_model_update))
+ self.assertTrue('id^%s' % 6231 in
+ volumes_model_update[0]['provider_location'])
+ self.assertTrue('id^%s' % 6232 in
+ volumes_model_update[1]['provider_location'])
+
+ delete_temp_snap_cmd = [
+ mock.call(*td.DELETE_CG_SNAPSHOT(temp_snap_name))]
+ fake_cli.assert_has_calls(delete_temp_snap_cmd)
+
+ @mock.patch.object(emc_vnx_cli, 'LOG')
+ @mock.patch.object(emc_vnx_cli.CommandLineHelper,
+ 'delete_cgsnapshot')
+ def test_delete_temp_cgsnapshot_failed_will_not_raise_exception(
+ self, mock_delete_cgsnapshot, mock_logger):
+ temp_snap_name = 'fake_temp'
+ self.driverSetup()
+ mock_delete_cgsnapshot.side_effect = exception.EMCVnxCLICmdError(
+ cmd='fake_cmd', rc=200, out='fake_output')
+ self.driver.cli._delete_temp_cgsnap(temp_snap_name)
+ mock_delete_cgsnapshot.assert_called_once_with(temp_snap_name)
+ self.assertTrue(mock_logger.warning.called)
+
+ @mock.patch.object(emc_vnx_cli.CreateSMPTask, 'execute',
+ mock.Mock(side_effect=exception.EMCVnxCLICmdError(
+ cmd='fake_cmd', rc=20, out='fake_output')))
+ @mock.patch.object(emc_vnx_cli.CreateSMPTask, 'revert',
+ mock.Mock())
+ def test_create_consistencygroup_from_cg_roll_back(self):
+ new_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ new_cg.id = 'new_cg_id'
+ vol1_in_new_cg = self.testData.test_volume_cg.copy()
+ vol1_in_new_cg.update(
+ {'name': 'vol1_in_cg',
+ 'id': '111111',
+ 'consistencygroup_id': 'new_cg_id',
+ 'provider_location': None})
+ src_cg = fake_consistencygroup.fake_consistencyobject_obj(
+ None, **self.testData.test_cg)
+ src_cg.id = 'src_cg_id'
+ vol1_in_src_cg = self.testData.test_volume_cg.copy()
+ vol1_in_src_cg.update(
+ {'name': 'vol1_in_src_cg',
+ 'id': '111110000',
+ 'consistencygroup_id': 'src_cg_id',
+ 'provider_location': None})
+ temp_snap_name = 'temp_snapshot_for_%s' % new_cg['id']
+ td = self.testData
+ commands = [td.CREATE_CG_SNAPSHOT(src_cg['id'], temp_snap_name),
+ td.DELETE_CG_SNAPSHOT(temp_snap_name)]
+ results = [SUCCEED, SUCCEED]
+
+ fake_cli = self.driverSetup(commands, results)
+
+ self.assertRaises(
+ exception.EMCVnxCLICmdError,
+ self.driver.create_consistencygroup_from_src,
+ None, new_cg, [vol1_in_new_cg],
+ cgsnapshot=None, snapshots=None,
+ source_cg=src_cg, source_vols=[vol1_in_src_cg])
+
+ rollback_cmd = [
+ mock.call(*td.DELETE_CG_SNAPSHOT(temp_snap_name))]
+ fake_cli.assert_has_calls(rollback_cmd)
+
def test_deregister_initiator(self):
fake_cli = self.driverSetup()
self.driver.cli.destroy_empty_sg = True
LOG.info(_LI('Consistency group %s was deleted '
'successfully.'), cg_name)
- def create_cgsnapshot(self, cgsnapshot):
+ def create_cgsnapshot(self, cg_name, snap_name):
"""Create a cgsnapshot (snap group)."""
- cg_name = cgsnapshot['consistencygroup_id']
- snap_name = cgsnapshot['id']
create_cg_snap_cmd = ('-np', 'snap', '-create',
'-res', cg_name,
'-resType', 'CG',
class EMCVnxCliBase(object):
"""This class defines the functions to use the native CLI functionality."""
- VERSION = '06.00.00'
+ VERSION = '07.00.00'
stats = {'driver_version': VERSION,
'storage_protocol': None,
'vendor_name': 'EMC',
{'group_name': cgsnapshot['consistencygroup_id']})
try:
- self._client.create_cgsnapshot(cgsnapshot)
+ self._client.create_cgsnapshot(cgsnapshot['consistencygroup_id'],
+ cgsnapshot['id'])
for snapshot in snapshots:
snapshot['status'] = 'available'
except Exception:
pass
def create_consistencygroup_from_src(self, context, group, volumes,
- cgsnapshot=None, snapshots=None):
+ cgsnapshot=None, snapshots=None,
+ source_cg=None, source_vols=None):
"""Creates a consistency group from cgsnapshot."""
+ if cgsnapshot and snapshots and not source_cg:
+ return self._create_consisgroup_from_cgsnapshot(
+ group, volumes, cgsnapshot, snapshots)
+ elif source_cg and source_vols and not cgsnapshot:
+ return self._clone_consisgroup(
+ group, volumes, source_cg, source_vols)
+ else:
+ msg = _("create_consistencygroup_from_src supports a "
+ "cgsnapshot source or a consistency group source. "
+ "Multiple sources cannot be used.")
+ raise exception.InvalidInput(reason=msg)
- if not cgsnapshot or not snapshots:
- msg = _("create_consistencygroup_from_src only supports a "
- "cgsnapshot source, other sources cannot be used.")
- raise exception.InvalidInput(msg)
+ def _clone_consisgroup(self, group, volumes, source_cg, source_vols):
+ temp_cgsnapshot_name = 'temp_snapshot_for_{}'.format(group.id)
+ store_spec = {
+ 'group': group,
+ 'snapshot': {'id': temp_cgsnapshot_name,
+ 'consistencygroup_id': source_cg.id},
+ 'snap_name': temp_cgsnapshot_name,
+ 'source_lun_id': None,
+ 'client': self._client
+ }
+ flow_name = 'clone_consisgroup'
+ snap_build_tasks = [CreateSnapshotTask()]
- flow_name = 'create_consistencygroup_from_cgsnapshot'
- work_flow = linear_flow.Flow(flow_name)
- copied_snapshot_name = 'temp_snapshot_for_%s' % group['id']
+ volume_model_updates = self._create_cg_from_cgsnap_use_workflow(
+ flow_name, snap_build_tasks, store_spec,
+ volumes, source_vols)
+
+ self._delete_temp_cgsnap(temp_cgsnapshot_name)
+
+ LOG.info(_LI('Consistency group %(cg)s is created successfully.'),
+ {'cg': group.id})
+
+ return None, volume_model_updates
+
+ def _create_consisgroup_from_cgsnapshot(self, group, volumes,
+ cgsnapshot, snapshots):
+ flow_name = 'create_consisgroup_from_cgsnapshot'
+ copied_snapshot_name = 'temp_snapshot_for_%s' % group.id
store_spec = {
'group': group,
'src_snap_name': cgsnapshot['id'],
'client': self._client
}
- work_flow.add(CopySnapshotTask(),
- AllowReadWriteOnSnapshotTask())
+ snap_build_tasks = [CopySnapshotTask(),
+ AllowReadWriteOnSnapshotTask()]
+
+ src_vols = map(lambda snap: snap.volume, snapshots)
+
+ volume_model_updates = self._create_cg_from_cgsnap_use_workflow(
+ flow_name, snap_build_tasks, store_spec, volumes, src_vols)
+
+ self._delete_temp_cgsnap(copied_snapshot_name)
+
+ LOG.info(_LI('Consistency group %(cg)s is created successfully.'),
+ {'cg': group.id})
+
+ return None, volume_model_updates
+
+ def _delete_temp_cgsnap(self, snap):
+ try:
+ self._client.delete_cgsnapshot(snap)
+ except exception.EMCVnxCLICmdError as ex:
+ LOG.warning(_LW('Delete the temporary cgsnapshot %(name)s failed. '
+ 'This temporary cgsnapshot can be deleted '
+ 'manually. '
+ 'Message: %(msg)s'),
+ {'name': snap,
+ 'msg': ex.kwargs['out']})
+ def _create_cg_from_cgsnap_use_workflow(self, flow_name, snap_build_tasks,
+ store_spec, volumes, source_vols):
+ work_flow = linear_flow.Flow(flow_name)
+ work_flow.add(*snap_build_tasks)
# Add tasks for each volumes in the consistency group
lun_id_key_template = 'new_lun_id_%s'
lun_data_key_template = 'vol_%s'
volume_model_updates = []
- for i, (volume, snap) in enumerate(zip(volumes, snapshots)):
+ for i, (volume, src_volume) in enumerate(zip(volumes, source_vols)):
specs = self.get_volumetype_extraspecs(volume)
- provisioning, tiering, snapcopy = (
+ provisioning, tiering, snap_copy = (
self._get_and_validate_extra_specs(specs))
- pool_name = self. get_target_storagepool(volume, snap['volume'])
+ pool_name = self.get_target_storagepool(volume, src_volume)
sub_store_spec = {
'volume': volume,
- 'source_vol_name': snap['volume_name'],
+ 'source_vol_name': src_volume['name'],
'pool_name': pool_name,
'dest_vol_name': volume['name'] + '_dest',
'volume_size': volume['size'],
'provisioning': provisioning,
'tiering': tiering,
'ignore_pool_full_threshold': self.ignore_pool_full_threshold,
- 'snapcopy': snapcopy
}
work_flow.add(
CreateSMPTask(name="CreateSMPTask%s" % i,
flow_engine = taskflow.engines.load(work_flow, store=store_spec)
flow_engine.run()
- # Delete copied snapshot
- try:
- self._client.delete_cgsnapshot(copied_snapshot_name)
- except exception.EMCVnxCLICmdError as ex:
- LOG.warning(_LW('Delete the temporary cgsnapshot %(name)s failed. '
- 'This temporary cgsnapshot can be deleted '
- 'manually. Consistency group %(cg)s is created '
- 'successfully from cgsnapshot %(cgsnapshot)s. '
- 'Message: %(msg)s'),
- {'name': copied_snapshot_name,
- 'cg': group['id'],
- 'cgsnapshot': cgsnapshot['id'],
- 'msg': ex.kwargs['out']})
-
for i, update in enumerate(volume_model_updates):
new_lun_id = flow_engine.storage.fetch(lun_id_key_template % i)
update['provider_location'] = (
self._build_provider_location(new_lun_id))
-
- return None, volume_model_updates
+ return volume_model_updates
def get_target_storagepool(self, volume, source_volume=None):
pool = vol_utils.extract_host(volume['host'], 'pool')
Reversion strategy: Detach the SMP.
"""
- def execute(self, client, volume, snapcopy, snap_name,
+ def execute(self, client, volume, snap_name,
*args, **kwargs):
LOG.debug('AttachSnapTask.execute')
client.attach_mount_point(volume['name'], snap_name)
LOG.debug('CreateSnapshotTask.execute')
# Create temp Snapshot
if snapshot['consistencygroup_id']:
- client.create_cgsnapshot(snapshot)
+ client.create_cgsnapshot(snapshot['consistencygroup_id'],
+ snapshot['id'])
else:
snapshot_name = snapshot['name']
volume_name = snapshot['volume_name']