diff --git a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py index 25d408c6e96..54481d235c9 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -325,7 +325,8 @@ class StorwizeSVCManagementSimulator(object): 'aux', 'cluster', 'linkbandwidthmbits', - 'backgroundcopyrate' + 'backgroundcopyrate', + 'copies' ] no_or_one_param_args = [ 'autoexpand', @@ -732,10 +733,25 @@ port_speed!N/A volume_info['uid'] = ('ABCDEF' * 3) + ('0' * 14) + volume_info['id'] mdiskgrp = kwargs['mdiskgrp'].strip('\'\"') + sec_pool = None + is_mirror_vol = False + if 'copies' in kwargs: + # it is a mirror volume + pool_split = mdiskgrp.split(':') + if len(pool_split) != 2: + raise exception.InvalidInput( + reason=_('mdiskgrp %s is invalid for mirror ' + 'volume') % kwargs['mdiskgrp']) + else: + is_mirror_vol = True + mdiskgrp = pool_split[0] + sec_pool = pool_split[1] + if mdiskgrp == kwargs['mdiskgrp']: raise exception.InvalidInput( reason=_('mdiskgrp missing quotes %s') % kwargs['mdiskgrp']) mdiskgrp_id = self._get_mdiskgrp_id(mdiskgrp) + sec_pool_id = self._get_mdiskgrp_id(sec_pool) volume_info['mdisk_grp_name'] = mdiskgrp volume_info['mdisk_grp_id'] = str(mdiskgrp_id) @@ -803,6 +819,16 @@ port_speed!N/A 'easy_tier': volume_info['easy_tier'], 'compressed_copy': volume_info['compressed_copy']} volume_info['copies'] = {'0': vol_cp} + if is_mirror_vol: + vol_cp1 = {'id': '1', + 'status': 'online', + 'sync': 'yes', + 'primary': 'no', + 'mdisk_grp_id': str(sec_pool_id), + 'mdisk_grp_name': sec_pool, + 'easy_tier': volume_info['easy_tier'], + 'compressed_copy': volume_info['compressed_copy']} + volume_info['copies']['1'] = vol_cp1 if volume_info['name'] in self._volumes_list: return self._errors['CMMVC6035E'] @@ -1588,32 +1614,17 @@ port_speed!N/A return self._errors['CMMVC5707E'] mdiskgrp = kwargs['mdiskgrp'].strip('\'\"') vdisk = kwargs['vdisk'].strip('\'\"') + copy_id = kwargs['copy'] + if vdisk not in self._volumes_list: + return self._errors['CMMVC5753E'] + mdiskgrp_id = str(self._get_mdiskgrp_id(mdiskgrp)) - if vdisk in self._volumes_list: - curr_mdiskgrp = self._volumes_list - else: - for pool in self._other_pools: - if vdisk in pool: - curr_mdiskgrp = pool - break - else: - return self._errors['CMMVC5754E'] + self._volumes_list[vdisk]['mdisk_grp_name'] = mdiskgrp + self._volumes_list[vdisk]['mdisk_grp_id'] = mdiskgrp_id - if mdiskgrp == self._flags['storwize_svc_volpool_name']: - tgt_mdiskgrp = self._volumes_list - elif mdiskgrp == 'openstack2': - tgt_mdiskgrp = self._other_pools['openstack2'] - elif mdiskgrp == 'openstack3': - tgt_mdiskgrp = self._other_pools['openstack3'] - else: - return self._errors['CMMVC5754E'] - - if curr_mdiskgrp == tgt_mdiskgrp: - return self._errors['CMMVC6430E'] - - vol = curr_mdiskgrp[vdisk] - tgt_mdiskgrp[vdisk] = vol - del curr_mdiskgrp[vdisk] + vol = self._volumes_list[vdisk] + vol['copies'][copy_id]['mdisk_grp_name'] = mdiskgrp + vol['copies'][copy_id]['mdisk_grp_id'] = mdiskgrp_id return ('', '') def _cmd_addvdiskcopy(self, **kwargs): @@ -1629,6 +1640,7 @@ port_speed!N/A if mdiskgrp == kwargs['mdiskgrp']: raise exception.InvalidInput( reason=_('mdiskgrp missing quotes %s') % kwargs['mdiskgrp']) + auto_del = True if 'autodelete' in kwargs else False copy_info = {} copy_info['id'] = self._find_unused_id(vol['copies']) @@ -1649,6 +1661,14 @@ port_speed!N/A else: copy_info['compressed_copy'] = 'no' vol['copies'][copy_info['id']] = copy_info + if auto_del: + del_copy_id = None + for v in vol['copies'].values(): + if v['id'] != copy_info['id']: + del_copy_id = v['id'] + break + if del_copy_id: + del vol['copies'][del_copy_id] return ('Vdisk [%(vid)s] copy [%(cid)s] successfully created' % {'vid': vol['id'], 'cid': copy_info['id']}, '') @@ -1725,6 +1745,8 @@ port_speed!N/A for key, value in kwargs.items(): if key == 'easytier': vol['easy_tier'] = value + for copy in vol['copies'].values(): + vol['copies'][copy['id']]['easy_tier'] = value continue if key == 'warning': vol['warning'] = value.rstrip('%') @@ -1749,6 +1771,9 @@ port_speed!N/A return ('', err) if key in params: vol[key] = value + if key == 'autoexpand': + for copy in vol['copies'].values(): + vol['copies'][copy['id']]['autoexpand'] = value else: err = self._errors['CMMVC5709E'][1] % {'VALUE': key} return ('', err) @@ -3447,6 +3472,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver._helpers.check_fcmapping_interval = 0 self.mock_object(storwize_svc_iscsi.StorwizeSVCISCSIDriver, 'DEFAULT_GR_SLEEP', 0) + self._create_test_volume_types() def _set_flag(self, flag, value, configuration=None): if not configuration: @@ -3465,6 +3491,11 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): is_vol_defined = self.driver._helpers.is_vdisk_defined(name) self.assertEqual(exists, is_vol_defined) + def _create_test_volume_types(self): + spec = {'mirror_pool': 'openstack1'} + self.mirror_vol_type = self._create_volume_type(spec, 'mirror_type') + self.default_vol_type = self._create_volume_type(None, 'default_type') + def test_storwize_svc_connectivity(self): # Make sure we detect if the pool doesn't exist no_exist_pool = 'i-dont-exist-%s' % random.randint(10000, 99999) @@ -3656,21 +3687,26 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.assertEqual(self._driver.configuration.storwize_san_secondary_ip, self._driver.active_ip) - def _generate_vol_info(self, vol_name, vol_id): + def _create_volume_type(self, opts, type_name): + type_ref = volume_types.create(self.ctxt, type_name, opts) + vol_type = objects.VolumeType.get_by_id(self.ctxt, type_ref['id']) + return vol_type + + def _generate_vol_info(self, vol_type=None, size=10): pool = _get_test_pool() - prop = {'mdisk_grp_name': pool} - if vol_name: - prop.update(volume_name=vol_name, - volume_id=vol_id, - volume_size=10) - else: - prop.update(size=10, - volume_type_id=None, - mdisk_grp_name=pool, - host='openstack@svc#%s' % pool) + prop = {'size': size, + 'host': 'openstack@svc#%s' % pool} + if vol_type: + prop['volume_type_id'] = vol_type.id vol = testutils.create_volume(self.ctxt, **prop) return vol + def _generate_snap_info(self, vol_id, size=10): + prop = {'volume_id': vol_id, + 'volume_size': size} + snap = testutils.create_snapshot(self.ctxt, **prop) + return snap + def _create_volume(self, **kwargs): pool = _get_test_pool() prop = {'host': 'openstack@svc#%s' % pool, @@ -3739,7 +3775,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def _create_test_vol(self, opts): ctxt = testutils.get_test_admin_context() type_ref = volume_types.create(ctxt, 'testtype', opts) - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() volume.volume_type_id = type_ref['id'] volume.volume_typ = objects.VolumeType.get_by_id(ctxt, type_ref['id']) @@ -3761,7 +3797,8 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): 'qos': None, 'replication': False, 'stretched_cluster': None, - 'nofmtdisk': False} + 'nofmtdisk': False, + 'mirror_pool': None} return opt @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_qos') @@ -3792,7 +3829,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_storwize_svc_snapshots(self): vol1 = self._create_volume() - snap1 = self._generate_vol_info(vol1['name'], vol1['id']) + snap1 = self._generate_snap_info(vol1.id) # Test timeout and volume cleanup self._set_flag('storwize_svc_flashcopy_timeout', 1) @@ -3824,7 +3861,8 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self._assert_vol_exists(snap1['name'], True) # Try to create a snapshot from an non-existing volume - should fail - snap_novol = self._generate_vol_info('undefined-vol', '12345') + vol2 = self._generate_vol_info() + snap_novol = self._generate_snap_info(vol2.id) self.assertRaises(exception.VolumeDriverException, self.driver.create_snapshot, snap_novol) @@ -3880,14 +3918,14 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_storwize_svc_create_volume_from_snapshot(self): vol1 = self._create_volume() - snap1 = self._generate_vol_info(vol1['name'], vol1['id']) + snap1 = self._generate_snap_info(vol1.id) self.driver.create_snapshot(snap1) - vol2 = self._generate_vol_info(None, None) - vol3 = self._generate_vol_info(None, None) + vol2 = self._generate_vol_info() + vol3 = self._generate_vol_info() # Try to create a volume from a non-existing snapshot - snap_novol = self._generate_vol_info('undefined-vol', '12345') - vol_novol = self._generate_vol_info(None, None) + vol_novol = self._generate_vol_info() + snap_novol = self._generate_snap_info(vol_novol.id) self.assertRaises(exception.VolumeDriverException, self.driver.create_volume_from_snapshot, vol_novol, @@ -3938,10 +3976,10 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_storwize_svc_create_volfromsnap_clone_with_qos(self, add_vdisk_qos): vol1 = self._create_volume() - snap1 = self._generate_vol_info(vol1['name'], vol1['id']) + snap1 = self._generate_snap_info(vol1.id) self.driver.create_snapshot(snap1) - vol2 = self._generate_vol_info(None, None) - vol3 = self._generate_vol_info(None, None) + vol2 = self._generate_vol_info() + vol3 = self._generate_vol_info() fake_opts = self._get_default_opts() # Succeed @@ -4002,12 +4040,12 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_storwize_svc_delete_vol_with_fcmap(self): vol1 = self._create_volume() # create two snapshots - snap1 = self._generate_vol_info(vol1['name'], vol1['id']) - snap2 = self._generate_vol_info(vol1['name'], vol1['id']) + snap1 = self._generate_snap_info(vol1.id) + snap2 = self._generate_snap_info(vol1.id) self.driver.create_snapshot(snap1) self.driver.create_snapshot(snap2) - vol2 = self._generate_vol_info(None, None) - vol3 = self._generate_vol_info(None, None) + vol2 = self._generate_vol_info() + vol3 = self._generate_vol_info() # Create vol from the second snapshot if self.USESIM: @@ -4045,7 +4083,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_storwize_svc_volumes(self): # Create a first volume - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() self.driver.create_volume(volume) self.driver.ensure_export(None, volume) @@ -4067,7 +4105,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): volume) # Try to delete a volume that doesn't exist (should not fail) - vol_no_exist = self._generate_vol_info(None, None) + vol_no_exist = self._generate_vol_info() self.driver.delete_volume(vol_no_exist) # Ensure export for volume that doesn't exist (should not fail) self.driver.ensure_export(None, vol_no_exist) @@ -4076,7 +4114,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.delete_volume(volume) def test_storwize_svc_volume_name(self): - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() self.driver.create_volume(volume) self.driver.ensure_export(None, volume) @@ -4154,7 +4192,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.do_setup(None) rand_id = random.randint(10000, 99999) - volume1 = self._generate_vol_info(None, None) + volume1 = self._generate_vol_info() self.driver.create_volume(volume1) self._assert_vol_exists(volume1['name'], True) @@ -4199,21 +4237,21 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Fail creating a snapshot - will force delete the snapshot if self.USESIM and False: - snap = self._generate_vol_info(master['name'], master['id']) + snap = self._generate_snap_info(master.id) self.sim.error_injection('startfcmap', 'bad_id') self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_snapshot, snap) self._assert_vol_exists(snap['name'], False) # Delete a snapshot - snap = self._generate_vol_info(master['name'], master['id']) + snap = self._generate_snap_info(master.id) self.driver.create_snapshot(snap) self._assert_vol_exists(snap['name'], True) self.driver.delete_snapshot(snap) self._assert_vol_exists(snap['name'], False) # Delete a volume with snapshots (regular) - snap = self._generate_vol_info(master['name'], master['id']) + snap = self._generate_snap_info(master.id) self.driver.create_snapshot(snap) self._assert_vol_exists(snap['name'], True) self.driver.delete_volume(master) @@ -4221,7 +4259,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Fail create volume from snapshot - will force delete the volume if self.USESIM: - volfs = self._generate_vol_info(None, None) + volfs = self._generate_vol_info() self.sim.error_injection('startfcmap', 'bad_id') self.sim.error_injection('lsfcmap', 'speed_up') self.assertRaises(exception.VolumeBackendAPIException, @@ -4230,7 +4268,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self._assert_vol_exists(volfs['name'], False) # Create volume from snapshot and delete it - volfs = self._generate_vol_info(None, None) + volfs = self._generate_vol_info() if self.USESIM: self.sim.error_injection('lsfcmap', 'speed_up') self.driver.create_volume_from_snapshot(volfs, snap) @@ -4239,7 +4277,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self._assert_vol_exists(volfs['name'], False) # Create volume from snapshot and delete the snapshot - volfs = self._generate_vol_info(None, None) + volfs = self._generate_vol_info() if self.USESIM: self.sim.error_injection('lsfcmap', 'speed_up') self.driver.create_volume_from_snapshot(volfs, snap) @@ -4248,7 +4286,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Fail create clone - will force delete the target volume if self.USESIM: - clone = self._generate_vol_info(None, None) + clone = self._generate_vol_info() self.sim.error_injection('startfcmap', 'bad_id') self.sim.error_injection('lsfcmap', 'speed_up') self.assertRaises(exception.VolumeBackendAPIException, @@ -4257,7 +4295,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self._assert_vol_exists(clone['name'], False) # Create the clone, delete the source and target - clone = self._generate_vol_info(None, None) + clone = self._generate_vol_info() if self.USESIM: self.sim.error_injection('lsfcmap', 'speed_up') self.driver.create_cloned_volume(clone, volfs) @@ -4306,12 +4344,13 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_get_pool(self): ctxt = testutils.get_test_admin_context() type_ref = volume_types.create(ctxt, 'testtype', None) - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() volume.volume_type_id = type_ref['id'] volume.volume_type = objects.VolumeType.get_by_id(ctxt, type_ref['id']) self.driver.create_volume(volume) - self.assertEqual(volume['mdisk_grp_name'], + vol = self.driver._helpers.get_vdisk_attributes(volume.name) + self.assertEqual(vol['mdisk_grp_name'], self.driver.get_pool(volume)) self.driver.delete_volume(volume) @@ -4325,7 +4364,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.assertAlmostEqual(vol_size, 13) - snap = self._generate_vol_info(volume['name'], volume['id']) + snap = self._generate_snap_info(volume.id) self.driver.create_snapshot(snap) self._assert_vol_exists(snap['name'], True) self.assertRaises(exception.VolumeDriverException, @@ -4537,10 +4576,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) - volume = self._generate_vol_info(None, None) old_type = objects.VolumeType.get_by_id(ctxt, old_type_ref['id']) - volume['volume_type'] = old_type + volume = self._generate_vol_info(old_type) volume['host'] = host['host'] new_type = objects.VolumeType.get_by_id(ctxt, new_type_ref['id']) @@ -4629,10 +4667,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) - volume = self._generate_vol_info(None, None) old_type = objects.VolumeType.get_by_id(ctxt, old_type_ref['id']) - volume['volume_type'] = old_type + volume = self._generate_vol_info(old_type) volume['host'] = host['host'] new_type = objects.VolumeType.get_by_id(ctxt, new_type_ref['id']) @@ -4666,10 +4703,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) - volume = self._generate_vol_info(None, None) old_type = objects.VolumeType.get_by_id(ctxt, old_type_ref['id']) - volume['volume_type'] = old_type + volume = self._generate_vol_info(old_type) volume['host'] = host['host'] new_type = objects.VolumeType.get_by_id(ctxt, new_type_ref['id']) @@ -4801,7 +4837,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.assertNotIn(volume['id'], self.driver._vdiskcopyops) def test_storwize_create_volume_with_replication_disable(self): - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() model_update = self.driver.create_volume(volume) self.assertIsNone(model_update) @@ -4814,7 +4850,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2') # Create a type for repliation. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() volume_type = self._create_replication_volume_type(True) volume['volume_type_id'] = volume_type['id'] @@ -4907,11 +4943,11 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.do_setup(self.ctxt) # Create a source volume. - src_volume = self._generate_vol_info(None, None) + src_volume = self._generate_vol_info() self.driver.create_volume(src_volume) # Create a type for repliation. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() volume_type = self._create_replication_volume_type(True) volume['volume_type_id'] = volume_type['id'] @@ -4929,12 +4965,12 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.do_setup(self.ctxt) vol1 = self._create_volume() - snap = self._generate_vol_info(vol1['name'], vol1['id']) + snap = self._generate_snap_info(vol1.id) self.driver.create_snapshot(snap) - vol2 = self._generate_vol_info(None, None) + vol2 = self._generate_vol_info() # Create a type for repliation. - vol2 = self._generate_vol_info(None, None) + vol2 = self._generate_vol_info() volume_type = self._create_replication_volume_type(True) vol2['volume_type_id'] = volume_type['id'] @@ -5239,6 +5275,231 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): for volume in model_update[1]: self.assertEqual('deleted', volume['status']) + # mirror/strtch cluster volume test cases + def test_storwize_svc_create_mirror_volume(self): + # create mirror volume in invalid pool + spec = {'mirror_pool': 'invalid_pool'} + mirror_vol_type = self._create_volume_type(spec, 'invalid_mirror_type') + vol = self._generate_vol_info(mirror_vol_type) + self.assertRaises(exception.InvalidInput, + self.driver.create_volume, vol) + + spec = {'mirror_pool': 'openstack1'} + mirror_vol_type = self._create_volume_type(spec, 'test_mirror_type') + vol = self._generate_vol_info(mirror_vol_type) + self.driver.create_volume(vol) + self._assert_vol_exists(vol.name, True) + + copies = self.driver._helpers.get_vdisk_copies(vol.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + self.driver.delete_volume(vol) + self._assert_vol_exists(vol['name'], False) + + def test_storwize_svc_snapshots_mirror_volume(self): + vol1 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume(vol1) + + snap1 = self._generate_snap_info(vol1.id) + self._assert_vol_exists(snap1.name, False) + + self.driver.create_snapshot(snap1) + if self.USESIM: + self.sim.error_injection('lsfcmap', 'speed_up') + self._assert_vol_exists(snap1.name, True) + copies = self.driver._helpers.get_vdisk_copies(snap1.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + + self.driver.delete_snapshot(snap1) + self.driver.delete_volume(vol1) + + def test_storwize_svc_create_cloned_mirror_volume(self): + vol1 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume(vol1) + vol2 = self._generate_vol_info(self.mirror_vol_type) + + if self.USESIM: + self.sim.error_injection('lsfcmap', 'speed_up') + self.driver.create_cloned_volume(vol2, vol1) + self._assert_vol_exists(vol2.name, True) + copies = self.driver._helpers.get_vdisk_copies(vol2.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + + self.driver.delete_volume(vol2) + self._assert_vol_exists(vol2.name, False) + self.driver.delete_volume(vol1) + self._assert_vol_exists(vol1.name, False) + + def test_storwize_svc_create_mirror_volume_from_snapshot(self): + vol1 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume(vol1) + snap1 = self._generate_snap_info(vol1.id) + self.driver.create_snapshot(snap1) + + if self.USESIM: + self.sim.error_injection('lsfcmap', 'speed_up') + + vol2 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume_from_snapshot(vol2, snap1) + self._assert_vol_exists(vol2.name, True) + copies = self.driver._helpers.get_vdisk_copies(vol2.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + + self.driver.delete_volume(vol2) + self._assert_vol_exists(vol2['name'], False) + self.driver.delete_snapshot(snap1) + self._assert_vol_exists(snap1['name'], False) + self.driver.delete_volume(vol1) + self._assert_vol_exists(vol1['name'], False) + + @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_copy') + def test_storwize_svc_mirror_volume_migrate(self, add_vdisk_copy): + # use migratevdisk for mirror volume migration, rather than + # addvdiskcopy + self.driver.do_setup(None) + loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] + + ':openstack2') + host = {'host': 'openstack@svc#openstack2', + 'capabilities': {'location_info': loc}} + ctxt = context.get_admin_context() + vol1 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume(vol1) + copies = self.driver._helpers.get_vdisk_copies(vol1.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + + self.driver.migrate_volume(ctxt, vol1, host) + copies = self.driver._helpers.get_vdisk_copies(vol1.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack2') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + self.assertFalse(add_vdisk_copy.called) + self._delete_volume(vol1) + + @ddt.data(({'mirror_pool': 'openstack1'}, + {'mirror_pool': 'openstack1', 'compression': True}), + ({'compression': False}, + {'mirror_pool': 'openstack1', 'compression': True}), + ({}, {'mirror_pool': 'invalidpool'})) + @ddt.unpack + def test_storwize_svc_retype_mirror_volume_invalid(self, old_opts, + new_opts): + self.driver.do_setup(self.ctxt) + host = {'host': 'openstack@svc#openstack'} + ctxt = context.get_admin_context() + + vol_type1 = self._create_volume_type(old_opts, 'old') + vol_type2 = self._create_volume_type(new_opts, 'new') + diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id, + vol_type2.id) + vol1 = self._generate_vol_info(vol_type1) + self.driver.create_volume(vol1) + + self.assertRaises(exception.VolumeDriverException, + self.driver.retype, self.ctxt, vol1, + vol_type2, diff, host) + self.driver.delete_volume(vol1) + + @ddt.data(({'mirror_pool': 'openstack1'}, {}), + ({'mirror_pool': 'openstack1'}, {'mirror_pool': ''})) + @ddt.unpack + def test_storwize_retype_from_mirror_to_none_mirror(self, + old_opts, new_opts): + self.driver.do_setup(self.ctxt) + host = {'host': 'openstack@svc#openstack'} + ctxt = context.get_admin_context() + + vol_type1 = self._create_volume_type(old_opts, 'old') + vol_type2 = self._create_volume_type(new_opts, 'new') + diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id, + vol_type2.id) + vol1 = self._generate_vol_info(vol_type1) + self.driver.create_volume(vol1) + + self._assert_vol_exists(vol1.name, True) + copies = self.driver._helpers.lsvdiskcopy(vol1.name) + self.assertEqual(len(copies), 2) + + self.driver.retype(self.ctxt, vol1, vol_type2, diff, host) + copies = self.driver._helpers.lsvdiskcopy(vol1.name) + self.assertEqual(len(copies), 1) + copies = self.driver._helpers.get_vdisk_copies(vol1.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + + self.driver.delete_volume(vol1) + + @ddt.data(({}, {'mirror_pool': 'openstack1'}), + ({'mirror_pool': ''}, {'mirror_pool': 'openstack1'})) + @ddt.unpack + def test_storwize_retype_from_none_to_mirror_volume(self, + old_opts, new_opts): + self.driver.do_setup(self.ctxt) + host = {'host': 'openstack@svc#openstack'} + ctxt = context.get_admin_context() + + old_opts = {} + new_opts = {'mirror_pool': 'openstack1'} + vol_type1 = self._create_volume_type(old_opts, 'old') + vol_type2 = self._create_volume_type(new_opts, 'new') + diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id, + vol_type2.id) + vol1 = self._generate_vol_info(vol_type1) + self.driver.create_volume(vol1) + + self._assert_vol_exists(vol1.name, True) + copies = self.driver._helpers.lsvdiskcopy(vol1.name) + self.assertEqual(len(copies), 1) + + self.driver.retype(self.ctxt, vol1, vol_type2, diff, host) + copies = self.driver._helpers.lsvdiskcopy(vol1.name) + self.assertEqual(len(copies), 2) + copies = self.driver._helpers.get_vdisk_copies(vol1.name) + self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack') + self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1') + + self.driver.delete_volume(vol1) + + @ddt.data(({}, {'mirror_pool': 'openstack1'}), + ({'mirror_pool': ''}, {'mirror_pool': 'openstack1'}), + ({'mirror_pool': 'openstack1'}, {}), + ({'mirror_pool': 'openstack1'}, {'mirror_pool': ''}), + ({'mirror_pool': 'openstack1'}, {'mirror_pool': 'invalidpool'})) + @ddt.unpack + def test_storwize_manage_existing_mismatch_with_mirror_volume( + self, opts1, opts2): + self.driver.do_setup(self.ctxt) + vol_type1 = self._create_volume_type(opts1, 'vol_type1') + vol_type2 = self._create_volume_type(opts2, 'vol_type2') + vol1 = self._generate_vol_info(vol_type1) + self.driver.create_volume(vol1) + vol2 = self._generate_vol_info(vol_type2) + + ref = {'source-name': vol1.name} + self.assertRaises(exception.ManageExistingVolumeTypeMismatch, + self.driver.manage_existing, vol2, ref) + + self.driver.delete_volume(vol1) + + def test_storwize_manage_existing_with_mirror_volume(self): + self.driver.do_setup(self.ctxt) + vol1 = self._generate_vol_info(self.mirror_vol_type) + self.driver.create_volume(vol1) + uid_of_vol1 = self._get_vdisk_uid(vol1.name) + + opts1 = {'mirror_pool': 'openstack1'} + new_volume_type = self._create_volume_type(opts1, 'new_mirror_type') + new_volume = self._generate_vol_info(new_volume_type) + ref = {'source-name': vol1.name} + self.driver.manage_existing(new_volume, ref) + + # Check the uid of the volume which has been renamed. + uid_of_new_vol = self._get_vdisk_uid(new_volume.name) + self.assertEqual(uid_of_vol1, uid_of_new_vol) + + self.driver.delete_volume(new_volume) + def _create_volume_type_qos(self, extra_specs, fake_qos): # Generate a QoS volume type for volume. if extra_specs: @@ -5309,7 +5570,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): create_volume and then calling into the simulator to perform an lsvdisk directly. """ - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() self.driver.create_volume(volume) return (volume, self._get_vdisk_uid(volume['name'])) @@ -5321,14 +5582,14 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): a bad reference that the Storwize driver doesn't understand. We expect an exception to be raised. """ - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing_get_size, volume, ref) def test_manage_existing_get_size_bad_uid(self): """Error when the specified UUID does not exist.""" - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {'source-id': 'bad_uid'} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing_get_size, volume, ref) @@ -5336,7 +5597,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def test_manage_existing_get_size_bad_name(self): """Error when the specified name does not exist.""" - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {'source-name': 'bad_name'} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing_get_size, volume, ref) @@ -5350,19 +5611,19 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): """ # Error when neither UUID nor name are specified. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing, volume, ref) # Error when the specified UUID does not exist. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {'source-id': 'bad_uid'} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing, volume, ref) # Error when the specified name does not exist. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {'source-name': 'bad_name'} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing, volume, ref) @@ -5386,7 +5647,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): opts = {'rsize': -1, 'iogrp': 1} type_iogrp_ref = volume_types.create(ctxt, 'testtype4', opts) - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() ref = {'source-name': _volume['name']} fake_copy_thin = self._get_default_opts() @@ -5462,7 +5723,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Descriptor of the Cinder volume that we want to own the vdisk # referenced by uid. - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() # Submit the request to manage it. ref = {'source-id': uid} @@ -5490,7 +5751,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Descriptor of the Cinder volume that we want to own the vdisk # referenced by uid. - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() # Submit the request to manage it. ref = {'source-name': _volume['name']} @@ -5523,7 +5784,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Descriptor of the Cinder volume that we want to own the vdisk # referenced by uid. - volume = self._generate_vol_info(None, None) + volume = self._generate_vol_info() ref = {'source-id': uid} # Attempt to manage this disk, and except an exception beause the @@ -5555,7 +5816,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Descriptor of the Cinder volume that we want to own the vdisk # referenced by uid. - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() # Submit the request to manage it, specifying that it is OK to # manage a volume that is already attached. @@ -5589,7 +5850,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): # Descriptor of the Cinder volume that we want to own the vdisk # referenced by uid. - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() # Submit the request to manage it, specifying that it is OK to # manage a volume that is already attached. @@ -5856,6 +6117,7 @@ class StorwizeSSHTestCase(test.TestCase): 'fakevol', '1', 'gb', 'fakepool', opt, []) +@ddt.ddt class StorwizeSVCReplicationTestCase(test.TestCase): @mock.patch.object(time, 'sleep') def setUp(self, mock_sleep): @@ -5949,7 +6211,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase): return snap def _create_replica_volume_type(self, enable, - rep_type=storwize_const.METRO): + rep_type=storwize_const.METRO, + opts=None, vol_type_name=None): # Generate a volume type for volume repliation. if enable: if rep_type == storwize_const.METRO: @@ -5960,6 +6223,9 @@ class StorwizeSVCReplicationTestCase(test.TestCase): spec = {'replication_enabled': ' True', 'replication_type': ' global'} type_name = 'rep_global' + elif opts: + spec = opts + type_name = vol_type_name else: spec = {'replication_enabled': ' False'} type_name = "non_rep" @@ -6042,6 +6308,21 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver._active_backend_id = None self.driver._get_storwize_config() + @mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'create_vdisk') + def test_storwize_svc_create_stretch_volume_with_replication(self, + create_vdisk): + spec = {'mirror_pool': 'openstack1', + 'replication_enabled': ' True', + 'replication_type': ' global' + } + vol_type = self._create_replica_volume_type( + False, opts=spec, vol_type_name='test_type') + vol = self._generate_vol_info(vol_type) + self.assertRaises(exception.InvalidInput, + self.driver.create_volume, vol) + self.assertFalse(create_vdisk.called) + def test_storwize_create_volume_with_mirror_replication(self): # Set replication target. self.driver.configuration.set_override('replication_device', @@ -6133,6 +6414,43 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.delete_volume(src_volume) self.driver.delete_volume(volume) + @ddt.data(({'replication_enabled': ' True', + 'replication_type': ' global'}, + {'replication_enabled': ' True', + 'replication_type': ' metro'}), + ({'replication_enabled': ' True', + 'replication_type': ' metro'}, + {'replication_enabled': ' True', + 'replication_type': ' global'}), + ({'replication_enabled': ' True', + 'replication_type': ' metro'}, + {'mirror_pool': 'openstack1'}), + ({'mirror_pool': 'openstack1'}, + {'mirror_pool': 'openstack1', + 'replication_enabled': ' True', + 'replication_type': ' metro'}), + ({'replication_enabled': ' False'}, + {'mirror_pool': 'openstack1', + 'replication_enabled': ' True', + 'replication_type': ' metro'})) + @ddt.unpack + def test_storwize_retype_invalid_replication(self, old_opts, new_opts): + # Set replication target + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + self.driver.do_setup(self.ctxt) + host = {'host': 'openstack@svc#openstack'} + old_type = self._create_replica_volume_type( + False, opts=old_opts, vol_type_name='test_old_type') + + volume, model_update = self._create_test_volume(old_type) + new_type = self._create_replica_volume_type( + False, opts=new_opts, vol_type_name='test_new_type') + diff, _equal = volume_types.volume_types_diff( + self.ctxt, new_type['id'], old_type['id']) + self.assertRaises(exception.VolumeDriverException, self.driver.retype, + self.ctxt, volume, new_type, diff, host) + def test_storwize_retype_from_mirror_to_none_replication(self): # Set replication target self.driver.configuration.set_override('replication_device', @@ -6143,13 +6461,6 @@ class StorwizeSVCReplicationTestCase(test.TestCase): volume, model_update = self._create_test_volume(self.mm_type) self.assertEqual('enabled', model_update['replication_status']) - diff, _equal = volume_types.volume_types_diff( - self.ctxt, self.mm_type['id'], self.gm_type['id']) - # Change the mirror type - self.assertRaises(exception.VolumeDriverException, - self.driver.retype, self.ctxt, - volume, self.gm_type, diff, host) - diff, _equal = volume_types.volume_types_diff( self.ctxt, self.non_replica_type['id'], self.mm_type['id']) # Disable replica @@ -6275,6 +6586,33 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.delete_volume(rep_volume) + @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'rename_vdisk') + @mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'get_relationship_info') + def test_storwize_update_migrated_replication_volume( + self, get_rp_info, rename_vdisk): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + self.driver.do_setup(self.ctxt) + + # Create replication volume. + backend_volume, model_update = self._create_test_volume(self.mm_type) + volume, model_update = self._create_test_volume(self.mm_type) + get_rp_info.side_effect = [{'aux_vdisk_name': 'aux_test'}] + model_update = self.driver.update_migrated_volume(self.ctxt, volume, + backend_volume, + 'available') + aux_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + volume.name) + rename_vdisk.assert_called_with('aux_test', aux_vol) + self.assertEqual({'_name_id': None}, model_update) + + rename_vdisk.reset_mock() + rename_vdisk.side_effect = exception.VolumeBackendAPIException + model_update = self.driver.update_migrated_volume(self.ctxt, volume, + backend_volume, + 'available') + self.assertEqual({'_name_id': backend_volume.id}, model_update) + def test_storwize_delete_volume_with_mirror_replication(self): # Set replication target. self.driver.configuration.set_override('replication_device', diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py index 2da247b1414..b9f9f78aac7 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -120,6 +120,10 @@ storwize_svc_opts = [ help='Specifies the Storwize FlashCopy copy rate to be used ' 'when creating a full volume copy. The default is rate ' 'is 50, and the valid rates are 1-100.'), + cfg.StrOpt('storwize_svc_mirror_pool', + default=None, + help='Specifies the name of the pool in which mirrored copy ' + 'is stored. Example: "pool2"'), ] CONF = cfg.CONF @@ -196,7 +200,13 @@ class StorwizeSSH(object): def lsmdiskgrp(self, pool): ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', '"%s"' % pool] - return self.run_ssh_info(ssh_cmd)[0] + try: + return self.run_ssh_info(ssh_cmd)[0] + except exception.VolumeBackendAPIException as ex: + LOG.warning("Failed to get pool %(pool)s info. " + "Exception: %(ex)s.", {'pool': pool, + 'ex': ex}) + return None def lsiogrp(self): ssh_cmd = ['svcinfo', 'lsiogrp', '-delim', '!'] @@ -542,9 +552,12 @@ class StorwizeSSH(object): ssh_cmd = ['svctask', 'rmfcconsistgrp', '-force', fc_consist_group] return self.run_ssh_assert_no_output(ssh_cmd) - def addvdiskcopy(self, vdisk, dest_pool, params): + def addvdiskcopy(self, vdisk, dest_pool, params, auto_delete): ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp', - '"%s"' % dest_pool, '"%s"' % vdisk]) + '"%s"' % dest_pool]) + if auto_delete: + ssh_cmd += ['-autodelete'] + ssh_cmd += ['"%s"' % vdisk] return self.run_ssh_check_created(ssh_cmd) def lsvdiskcopy(self, vdisk, copy_id=None): @@ -579,6 +592,11 @@ class StorwizeSSH(object): '-filtervalue', 'node_id=%s' % node_id] return self.run_ssh_info(ssh_cmd, with_header=True) + def migratevdisk(self, vdisk, dest_pool, copy_id='0'): + ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool, '-copy', + copy_id, '-vdisk', vdisk] + self.run_ssh_assert_no_output(ssh_cmd) + class StorwizeHelpers(object): @@ -653,6 +671,11 @@ class StorwizeHelpers(object): """Return attributes for the specified pool.""" return self.ssh.lsmdiskgrp(pool) + def is_pool_defined(self, pool_name): + """Check if vdisk is defined.""" + attrs = self.get_pool_attrs(pool_name) + return attrs is not None + def get_available_io_groups(self): """Return list of available IO groups.""" iogrps = [] @@ -1030,7 +1053,8 @@ class StorwizeHelpers(object): 'qos': None, 'stretched_cluster': cluster_partner, 'replication': False, - 'nofmtdisk': config.storwize_svc_vol_nofmtdisk} + 'nofmtdisk': config.storwize_svc_vol_nofmtdisk, + 'mirror_pool': config.storwize_svc_mirror_pool} return opt @staticmethod @@ -1225,7 +1249,7 @@ class StorwizeHelpers(object): return opts @staticmethod - def _get_vdisk_create_params(opts): + def _get_vdisk_create_params(opts, add_copies=False): easytier = 'on' if opts['easytier'] else 'off' if opts['rsize'] == -1: params = [] @@ -1243,14 +1267,27 @@ class StorwizeHelpers(object): else: params.extend(['-grainsize', str(opts['grainsize'])]) + if add_copies and opts['mirror_pool']: + params.extend(['-copies', '2']) + params.extend(['-easytier', easytier]) return params def create_vdisk(self, name, size, units, pool, opts): name = '"%s"' % name LOG.debug('Enter: create_vdisk: vdisk %s.', name) - params = self._get_vdisk_create_params(opts) - self.ssh.mkvdisk(name, size, units, pool, opts, params) + mdiskgrp = pool + if opts['mirror_pool']: + if not self.is_pool_defined(opts['mirror_pool']): + raise exception.InvalidInput( + reason=_('The pool %s in which mirrored copy is stored ' + 'is invalid') % opts['mirror_pool']) + # The syntax of pool SVC expects is pool:mirror_pool in + # mdiskgrp for mirror volume + mdiskgrp = '%s:%s' % (pool, opts['mirror_pool']) + params = self._get_vdisk_create_params( + opts, add_copies=True if opts['mirror_pool'] else False) + self.ssh.mkvdisk(name, size, units, mdiskgrp, opts, params) LOG.debug('Leave: _create_vdisk: volume %s.', name) def get_vdisk_attributes(self, vdisk): @@ -1349,11 +1386,18 @@ class StorwizeHelpers(object): for snapshot in snapshots: opts = self.get_vdisk_params(config, state, snapshot['volume_type_id']) - + volume = snapshot.volume + if not volume: + msg = (_("Can't get volume from snapshot: %(id)s") + % {"id": snapshot.id}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + pool = utils.extract_host(volume.host, 'pool') self.create_flashcopy_to_consistgrp(snapshot['volume_name'], snapshot['name'], fc_consistgrp, - config, opts) + config, opts, False, + pool=pool) self.prepare_fc_consistgrp(fc_consistgrp, timeout) self.start_fc_consistgrp(fc_consistgrp) @@ -1382,7 +1426,7 @@ class StorwizeHelpers(object): try: for snapshot in snapshots: - self.ssh.rmvdisk(snapshot['name'], True) + self.delete_vdisk(snapshot['name'], True) except exception.VolumeBackendAPIException as err: model_update['status'] = ( fields.GroupSnapshotStatus.ERROR_DELETING) @@ -1744,7 +1788,8 @@ class StorwizeHelpers(object): def extend_vdisk(self, vdisk, amount): self.ssh.expandvdisksize(vdisk, amount) - def add_vdisk_copy(self, vdisk, dest_pool, volume_type, state, config): + def add_vdisk_copy(self, vdisk, dest_pool, volume_type, state, config, + auto_delete=False): """Add a vdisk copy in the given pool.""" resp = self.ssh.lsvdiskcopy(vdisk) if len(resp) > 1: @@ -1766,7 +1811,15 @@ class StorwizeHelpers(object): opts = self.get_vdisk_params(config, state, volume_type['id'], volume_type=volume_type) params = self._get_vdisk_create_params(opts) - new_copy_id = self.ssh.addvdiskcopy(vdisk, dest_pool, params) + try: + new_copy_id = self.ssh.addvdiskcopy(vdisk, dest_pool, params, + auto_delete) + except exception.VolumeBackendAPIException as e: + msg = (_('Unable to add vdiskcopy for volume %(vol)s. ' + 'Exception: %(err)s.'), + {'vol': vdisk, 'err': e}) + LOG.exception(msg) + raise exception.VolumeDriverException(message=msg) return (orig_copy_id, new_copy_id) def is_vdisk_copy_synced(self, vdisk, copy_id): @@ -1778,6 +1831,9 @@ class StorwizeHelpers(object): def rm_vdisk_copy(self, vdisk, copy_id): self.ssh.rmvdiskcopy(vdisk, copy_id) + def lsvdiskcopy(self, vdisk, copy_id=None): + return self.ssh.lsvdiskcopy(vdisk, copy_id) + @staticmethod def can_migrate_to_host(host, state): if 'location_info' not in host['capabilities']: @@ -1880,6 +1936,9 @@ class StorwizeHelpers(object): def change_vdisk_primary_copy(self, vdisk, copy_id): self.ssh.chvdisk(vdisk, ['-primary', copy_id]) + def migratevdisk(self, vdisk, dest_pool, copy_id='0'): + self.ssh.migratevdisk(vdisk, dest_pool, copy_id) + class CLIResponse(object): """Parse SVC CLI output and generate iterable.""" @@ -2148,11 +2207,9 @@ class StorwizeSVCCommonDriver(san.SanDriver, # Validate that the pool exists pools = self._get_backend_pools() for pool in pools: - try: - self._helpers.get_pool_attrs(pool) - except exception.VolumeBackendAPIException: - msg = _('Failed getting details for pool %s.') % pool - raise exception.InvalidInput(reason=msg) + if not self._helpers.is_pool_defined(pool): + reason = (_('Failed getting details for pool %s.') % pool) + raise exception.InvalidInput(reason=reason) def check_for_setup_error(self): """Ensure that the flags are set properly.""" @@ -2337,8 +2394,14 @@ class StorwizeSVCCommonDriver(san.SanDriver, opts = self._get_vdisk_params(volume['volume_type_id'], volume_metadata= volume.get('volume_metadata')) - pool = utils.extract_host(volume['host'], 'pool') + ctxt = context.get_admin_context() + rep_type = self._get_volume_replicated_type(ctxt, volume) + pool = utils.extract_host(volume['host'], 'pool') + if opts['mirror_pool'] and rep_type: + reason = _('Create mirror volume with replication enabled is ' + 'not supported.') + raise exception.InvalidInput(reason=reason) opts['iogrp'] = self._helpers.select_io_group(self._state, opts) self._helpers.create_vdisk(volume['name'], str(volume['size']), 'gb', pool, opts) @@ -2346,8 +2409,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._helpers.add_vdisk_qos(volume['name'], opts['qos']) model_update = None - ctxt = context.get_admin_context() - rep_type = self._get_volume_replicated_type(ctxt, volume) # The replication V2 has a higher priority than the replication V1. # Check if V2 is available first, then check if V1 is available. @@ -2370,8 +2431,9 @@ class StorwizeSVCCommonDriver(san.SanDriver, rep_type = self._get_volume_replicated_type(ctxt, volume) if rep_type: - self._aux_backend_helpers.delete_rc_volume(volume['name'], - target_vol=True) + if self._aux_backend_helpers: + self._aux_backend_helpers.delete_rc_volume(volume['name'], + target_vol=True) if not self._active_backend_id: self._master_backend_helpers.delete_rc_volume(volume['name']) else: @@ -2565,10 +2627,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._helpers.extend_vdisk(volume_name, extend_amt) LOG.debug('leave: _extend_volume_op: volume %s', volume['id']) - def add_vdisk_copy(self, volume, dest_pool, vol_type): + def add_vdisk_copy(self, volume, dest_pool, vol_type, auto_delete=False): return self._helpers.add_vdisk_copy(volume, dest_pool, vol_type, self._state, - self.configuration) + self.configuration, + auto_delete=auto_delete) def _add_vdisk_copy_op(self, ctxt, volume, new_op): metadata = self.db.volume_admin_metadata_get(ctxt.elevated(), @@ -3164,13 +3227,78 @@ class StorwizeSVCCommonDriver(san.SanDriver, else: vol_type = None - self._check_volume_copy_ops() - new_op = self.add_vdisk_copy(volume['name'], dest_pool, vol_type) - self._add_vdisk_copy_op(ctxt, volume, new_op) + resp = self._helpers.lsvdiskcopy(volume.name) + if len(resp) > 1: + copies = self._helpers.get_vdisk_copies(volume.name) + self._helpers.migratevdisk(volume.name, dest_pool, + copies['primary']['copy_id']) + else: + self.add_vdisk_copy(volume.name, dest_pool, vol_type, + auto_delete=True) + LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s', - {'id': volume['id'], 'host': host['host']}) + {'id': volume.id, 'host': host['host']}) return (True, None) + def _verify_retype_params(self, volume, new_opts, old_opts, need_copy, + change_mirror, new_rep_type, old_rep_type): + # Some volume parameters can not be changed or changed at the same + # time during volume retype operation. This function checks the + # retype parameters. + resp = self._helpers.lsvdiskcopy(volume.name) + if old_opts['mirror_pool'] and len(resp) == 1: + msg = (_('Unable to retype: volume %s is a mirrorred vol. But it ' + 'has only one copy in storage.') % volume.name) + raise exception.VolumeDriverException(message=msg) + + if need_copy: + # mirror volume can not add volume-copy again. + if len(resp) > 1: + msg = (_('Unable to retype: current action needs volume-copy. ' + 'A copy of volume %s exists. Adding another copy ' + 'would exceed the limit of 2 copies.') % volume.name) + raise exception.VolumeDriverException(message=msg) + if old_opts['mirror_pool'] or new_opts['mirror_pool']: + msg = (_('Unable to retype: current action needs volume-copy, ' + 'it is not allowed for mirror volume ' + '%s.') % volume.name) + raise exception.VolumeDriverException(message=msg) + + if change_mirror: + if (new_opts['mirror_pool'] and + not self._helpers.is_pool_defined( + new_opts['mirror_pool'])): + msg = (_('Unable to retype: The pool %s in which mirror copy ' + 'is stored is not valid') % new_opts['mirror_pool']) + raise exception.VolumeDriverException(message=msg) + + # There are three options for rep_type: None, metro, global + if new_rep_type or old_rep_type: + # If volume is replicated, can't copy + if need_copy or new_opts['mirror_pool'] or old_opts['mirror_pool']: + msg = (_('Unable to retype: current action needs volume-copy, ' + 'it is not allowed for replication type. ' + 'Volume = %s') % volume.id) + raise exception.VolumeDriverException(message=msg) + + if new_rep_type != old_rep_type: + old_io_grp = self._helpers.get_volume_io_group(volume.name) + if (old_io_grp not in + StorwizeHelpers._get_valid_requested_io_groups( + self._state, new_opts)): + msg = (_('Unable to retype: it is not allowed to change ' + 'replication type and io group at the same time.')) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + if new_rep_type and old_rep_type: + msg = (_('Unable to retype: it is not allowed to change ' + '%(old_rep_type)s volume to %(new_rep_type)s ' + 'volume.') % + {'old_rep_type': old_rep_type, + 'new_rep_type': new_rep_type}) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + def retype(self, ctxt, volume, new_type, diff, host): """Convert the volume to be of the new type. @@ -3206,6 +3334,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, vdisk_changes = [] need_copy = False + change_mirror = False + for key in all_keys: if old_opts[key] != new_opts[key]: if key in copy_keys: @@ -3218,38 +3348,18 @@ class StorwizeSVCCommonDriver(san.SanDriver, utils.extract_host(host['host'], 'pool')): need_copy = True + if old_opts['mirror_pool'] != new_opts['mirror_pool']: + change_mirror = True + # Check if retype affects volume replication model_update = None new_rep_type = self._get_specs_replicated_type(new_type) old_rep_type = self._get_volume_replicated_type(ctxt, volume) old_io_grp = self._helpers.get_volume_io_group(volume['name']) - - # There are three options for rep_type: None, metro, global - if new_rep_type != old_rep_type: - if (old_io_grp not in - StorwizeHelpers._get_valid_requested_io_groups( - self._state, new_opts)): - msg = (_('Unable to retype: it is not allowed to change ' - 'replication type and io group at the same time.')) - LOG.error(msg) - raise exception.VolumeDriverException(message=msg) - if new_rep_type and old_rep_type: - msg = (_('Unable to retype: it is not allowed to change ' - '%(old_rep_type)s volume to %(new_rep_type)s ' - 'volume.') % - {'old_rep_type': old_rep_type, - 'new_rep_type': new_rep_type}) - LOG.error(msg) - raise exception.VolumeDriverException(message=msg) - # If volume is replicated, can't copy - if need_copy: - msg = (_('Unable to retype: Current action needs volume-copy,' - ' it is not allowed when new type is replication.' - ' Volume = %s') % volume['id']) - raise exception.VolumeDriverException(message=msg) - new_io_grp = self._helpers.select_io_group(self._state, new_opts) + self._verify_retype_params(volume, new_opts, old_opts, need_copy, + change_mirror, new_rep_type, old_rep_type) if need_copy: self._check_volume_copy_ops() dest_pool = self._helpers.can_migrate_to_host(host, self._state) @@ -3259,10 +3369,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, retype_iogrp_property(volume, new_io_grp, old_io_grp) try: - new_op = self.add_vdisk_copy(volume['name'], - dest_pool, - new_type) - self._add_vdisk_copy_op(ctxt, volume, new_op) + self.add_vdisk_copy(volume['name'], dest_pool, new_type, + auto_delete=True) except exception.VolumeDriverException: # roll back changing iogrp property retype_iogrp_property(volume, old_io_grp, new_io_grp) @@ -3275,7 +3383,23 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._helpers.change_vdisk_options(volume['name'], vdisk_changes, new_opts, self._state) - + if change_mirror: + copies = self._helpers.get_vdisk_copies(volume.name) + if not old_opts['mirror_pool'] and new_opts['mirror_pool']: + # retype from non mirror vol to mirror vol + self.add_vdisk_copy(volume['name'], + new_opts['mirror_pool'], new_type) + elif old_opts['mirror_pool'] and not new_opts['mirror_pool']: + # retype from mirror vol to non mirror vol + secondary = copies['secondary'] + if secondary: + self._helpers.rm_vdisk_copy( + volume.name, secondary['copy_id']) + else: + # migrate the second copy to another pool. + self._helpers.migratevdisk( + volume.name, new_opts['mirror_pool'], + copies['secondary']['copy_id']) if new_opts['qos']: # Add the new QoS setting to the volume. If the volume has an # old QoS setting, it will be overwritten. @@ -3322,6 +3446,13 @@ class StorwizeSVCCommonDriver(san.SanDriver, original_volume_name = CONF.volume_name_template % volume['id'] try: self._helpers.rename_vdisk(current_name, original_volume_name) + rep_type = self._get_volume_replicated_type(ctxt, new_volume) + if rep_type: + rel_info = self._helpers.get_relationship_info(current_name) + aux_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + + original_volume_name) + self._aux_backend_helpers.rename_vdisk( + rel_info['aux_vdisk_name'], aux_vol) except exception.VolumeBackendAPIException: LOG.error('Unable to rename the logical volume ' 'for volume: %s', volume['id']) @@ -3357,6 +3488,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, rep_type = self._get_volume_replicated_type(ctxt, volume) vol_rep_type = None rel_info = self._helpers.get_relationship_info(vdisk['name']) + copies = self._helpers.get_vdisk_copies(vdisk['name']) if rel_info: vol_rep_type = rel_info['copy_type'] aux_info = self._aux_backend_helpers.get_system_info() @@ -3379,8 +3511,28 @@ class StorwizeSVCCommonDriver(san.SanDriver, opts = self._get_vdisk_params(volume['volume_type_id'], volume_metadata= volume.get('volume_metadata')) - vdisk_copy = self._helpers.get_vdisk_copy_attrs(vdisk['name'], '0') + resp = self._helpers.lsvdiskcopy(vdisk['name']) + expected_copy_num = 2 if opts['mirror_pool'] else 1 + if len(resp) != expected_copy_num: + msg = (_("Failed to manage existing volume due to mirror type " + "mismatch. Volume to be managed has %(resp_len)s " + "copies. mirror_pool of the chosen type is " + "%(mirror_pool)s.") % + {'resp_len': len(resp), + 'mirror_pool': opts['mirror_pool']}) + raise exception.ManageExistingVolumeTypeMismatch(reason=msg) + if (opts['mirror_pool']and opts['mirror_pool'] != + copies['secondary']['mdisk_grp_name']): + msg = (_("Failed to manage existing volume due to mirror pool " + "mismatch. The secondary pool of the volume to be " + "managed is %(sec_copy_pool)s. mirror_pool of the " + "chosen type is %(mirror_pool)s.") % + {'sec_copy_pool': copies['secondary']['mdisk_grp_name'], + 'mirror_pool': opts['mirror_pool']}) + raise exception.ManageExistingVolumeTypeMismatch( + reason=msg) + vdisk_copy = self._helpers.get_vdisk_copy_attrs(vdisk['name'], '0') if vdisk_copy['autoexpand'] == 'on' and opts['rsize'] == -1: msg = (_("Failed to manage existing volume due to " "the volume to be managed is thin, but " @@ -3418,15 +3570,14 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'opt_iogrp': opts['iogrp']}) raise exception.ManageExistingVolumeTypeMismatch(reason=msg) pool = utils.extract_host(volume['host'], 'pool') - if vdisk['mdisk_grp_name'] != pool: + if copies['primary']['mdisk_grp_name'] != pool: msg = (_("Failed to manage existing volume due to the " "pool of the volume to be managed does not " "match the backend pool. Pool of the " "volume to be managed is %(vdisk_pool)s. Pool " "of the backend is %(backend_pool)s.") % - {'vdisk_pool': vdisk['mdisk_grp_name'], - 'backend_pool': - self._get_backend_pools()}) + {'vdisk_pool': copies['primary']['mdisk_grp_name'], + 'backend_pool': pool}) raise exception.ManageExistingVolumeTypeMismatch(reason=msg) model_update = {} diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py index b49aaba1b5c..99adc9f0483 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py @@ -89,9 +89,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver): mode 2.1.1 - Update replication to version 2.1 2.2 - Add CG capability to generic volume groups + 2.2.1 - Add vdisk mirror/stretch cluster support """ - VERSION = "2.2" + VERSION = "2.2.1" # ThirdPartySystems wiki page CI_WIKI_NAME = "IBM_STORAGE_CI" diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py index 62ccbbb2229..9ccd5d34c85 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py @@ -89,9 +89,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver): mode 2.1.1 - Update replication to version 2.1 2.2 - Add CG capability to generic volume groups + 2.2.1 - Add vdisk mirror/stretch cluster support """ - VERSION = "2.2" + VERSION = "2.2.1" # ThirdPartySystems wiki page CI_WIKI_NAME = "IBM_STORAGE_CI" diff --git a/releasenotes/notes/ibm-storwzie-mirror-volume-ffe4c9bde78cdf1d.yaml b/releasenotes/notes/ibm-storwzie-mirror-volume-ffe4c9bde78cdf1d.yaml new file mode 100644 index 00000000000..c1fbe26e4f5 --- /dev/null +++ b/releasenotes/notes/ibm-storwzie-mirror-volume-ffe4c9bde78cdf1d.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add mirrored volume support in IBM SVC/Storwize driver.