From b03992b6161ea1852b2abad9f04062bebd51a10c Mon Sep 17 00:00:00 2001 From: zhaochy Date: Fri, 28 Apr 2017 17:07:24 +0800 Subject: [PATCH] Add gmcv support in SVC driver SVC Global Mirror with Change Volumes(gmcv) provides asynchronous replication based on point-in-time copies of data. Two properties: replication_type=" gmcv" and drivers:cycle_period_seconds=500 in volume type to support replication volume with gmcv. Here is an example to define a gmcv replication type: openstack volume type set --property replication_type=" gmcv" --property replication_enabled=' True' --property drivers:cycle_period_seconds=500 --property volume_backend_name=svc75 SVCGMCVReplicationType DocImpact Change-Id: I525f82dccf88bc085825523d7e899143e2190a96 Implements: blueprint gmcv-support-in-svc-driver --- .../volume/drivers/ibm/test_storwize_svc.py | 1066 ++++++++++++----- .../drivers/ibm/storwize_svc/replication.py | 323 +++-- .../ibm/storwize_svc/storwize_const.py | 6 +- .../ibm/storwize_svc/storwize_svc_common.py | 289 +++-- ...torwize-gmcv-support-8aceee3f40eddb9f.yaml | 8 + 5 files changed, 1148 insertions(+), 544 deletions(-) create mode 100644 releasenotes/notes/storwize-gmcv-support-8aceee3f40eddb9f.yaml 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 cf34d2ceb29..8cd16d7351d 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -155,6 +155,8 @@ class StorwizeSVCManagementSimulator(object): 'because it is not valid given the current ' 'relationship state.'), 'CMMVC5963E': ('', 'CMMVC5963E No direction has been defined.'), + 'CMMVC5713E': ('', 'CMMVC5713E Some parameters are mutually ' + 'exclusive.'), } self._fc_transitions = {'begin': {'make': 'idle_or_copied'}, @@ -214,6 +216,12 @@ class StorwizeSVCManagementSimulator(object): 'stop_access': 'idling', 'delete': 'end', 'delete_force': 'end'}, + 'consistent_copying': { + 'start': 'consistent_copying', + 'stop': 'consistent_stopped', + 'stop_access': 'idling', + 'delete': 'end', + 'delete_force': 'end'}, 'consistent_stopped': {'start': 'consistent_synchronized', 'stop': 'consistent_stopped', @@ -326,7 +334,11 @@ class StorwizeSVCManagementSimulator(object): 'cluster', 'linkbandwidthmbits', 'backgroundcopyrate', - 'copies' + 'copies', + 'cyclingmode', + 'cycleperiodseconds', + 'masterchange', + 'auxchange', ] no_or_one_param_args = [ 'autoexpand', @@ -1880,6 +1892,11 @@ port_speed!N/A if (self._volumes_list[master_vol]['capacity'] != self._volumes_list[aux_vol]['capacity']): return self._errors['CMMVC5754E'] + + cyclingmode = None + if 'cyclingmode' in kwargs: + cyclingmode = kwargs['cyclingmode'].strip('\'\"') + rcrel_info = {} rcrel_info['id'] = self._find_unused_id(self._rcrelationship_list) rcrel_info['name'] = 'rcrel' + rcrel_info['id'] @@ -1901,7 +1918,7 @@ port_speed!N/A rcrel_info['status'] = 'online' rcrel_info['sync'] = '' rcrel_info['copy_type'] = 'global' if 'global' in kwargs else 'metro' - rcrel_info['cycling_mode'] = '' + rcrel_info['cycling_mode'] = cyclingmode if cyclingmode else '' rcrel_info['cycle_period_seconds'] = '300' rcrel_info['master_change_vdisk_id'] = '' rcrel_info['master_change_vdisk_name'] = '' @@ -1918,47 +1935,64 @@ port_speed!N/A def _cmd_lsrcrelationship(self, **kwargs): rows = [] - rows.append(['id', 'name', 'master_cluster_id', 'master_cluster_name', - 'master_vdisk_id', 'master_vdisk_name', 'aux_cluster_id', - 'aux_cluster_name', 'aux_vdisk_id', 'aux_vdisk_name', - 'consistency_group_id', 'primary', - 'consistency_group_name', 'state', 'bg_copy_priority', - 'progress', 'freeze_time', 'status', 'sync', - 'copy_type', 'cycling_mode', 'cycle_period_seconds', - 'master_change_vdisk_id', 'master_change_vdisk_name', - 'aux_change_vdisk_id', 'aux_change_vdisk_name']) - # Assume we always get a filtervalue argument - filter_key = kwargs['filtervalue'].split('=')[0] - filter_value = kwargs['filtervalue'].split('=')[1] - for k, v in self._rcrelationship_list.items(): - if six.text_type(v[filter_key]) == filter_value: - self._rc_state_transition('wait', v) + if 'obj' in kwargs: + name = kwargs['obj'] + for k, v in self._rcrelationship_list.items(): + if six.text_type(v['name']) == name: + self._rc_state_transition('wait', v) - if self._next_cmd_error['lsrcrelationship'] == 'speed_up': - self._next_cmd_error['lsrcrelationship'] = '' - curr_state = v['status'] - while self._rc_state_transition('wait', v) == ("", ""): - if curr_state == v['status']: - break + if self._next_cmd_error['lsrcrelationship'] == 'speed_up': + self._next_cmd_error['lsrcrelationship'] = '' curr_state = v['status'] + while self._rc_state_transition('wait', v) == ("", ""): + if curr_state == v['status']: + break + curr_state = v['status'] - rows.append([v['id'], v['name'], v['master_cluster_id'], - v['master_cluster_name'], v['master_vdisk_id'], - v['master_vdisk_name'], v['aux_cluster_id'], - v['aux_cluster_name'], v['aux_vdisk_id'], - v['aux_vdisk_name'], v['consistency_group_id'], - v['primary'], v['consistency_group_name'], - v['state'], v['bg_copy_priority'], v['progress'], - v['freeze_time'], v['status'], v['sync'], - v['copy_type'], v['cycling_mode'], - v['cycle_period_seconds'], - v['master_change_vdisk_id'], - v['master_change_vdisk_name'], - v['aux_change_vdisk_id'], - v['aux_change_vdisk_name']]) + rows.append(['id', v['id']]) + rows.append(['name', v['name']]) + rows.append(['master_cluster_id', v['master_cluster_id']]) + rows.append(['master_cluster_name', + v['master_cluster_name']]) + rows.append(['master_vdisk_id', v['master_vdisk_id']]) + rows.append(['master_vdisk_name', v['master_vdisk_name']]) + rows.append(['aux_cluster_id', v['aux_cluster_id']]) + rows.append(['aux_cluster_name', v['aux_cluster_name']]) + rows.append(['aux_vdisk_id', v['aux_vdisk_id']]) + rows.append(['aux_vdisk_name', v['aux_vdisk_name']]) + rows.append(['consistency_group_id', + v['consistency_group_id']]) + rows.append(['primary', v['primary']]) + rows.append(['consistency_group_name', + v['consistency_group_name']]) + rows.append(['state', v['state']]) + rows.append(['bg_copy_priority', v['bg_copy_priority']]) + rows.append(['progress', v['progress']]) + rows.append(['freeze_time', v['freeze_time']]) + rows.append(['status', v['status']]) + rows.append(['sync', v['sync']]) + rows.append(['copy_type', v['copy_type']]) + rows.append(['cycling_mode', v['cycling_mode']]) + rows.append(['cycle_period_seconds', + v['cycle_period_seconds']]) + rows.append(['master_change_vdisk_id', + v['master_change_vdisk_id']]) + rows.append(['master_change_vdisk_name', + v['master_change_vdisk_name']]) + rows.append(['aux_change_vdisk_id', + v['aux_change_vdisk_id']]) + rows.append(['aux_change_vdisk_name', + v['aux_change_vdisk_name']]) - return self._print_info_cmd(rows=rows, **kwargs) + if 'nohdr' in kwargs: + for index in range(len(rows)): + rows[index] = ' '.join(rows[index][1:]) + if 'delim' in kwargs: + for index in range(len(rows)): + rows[index] = kwargs['delim'].join(rows[index]) + + return ('%s' % '\n'.join(rows), '') def _cmd_startrcrelationship(self, **kwargs): if 'obj' not in kwargs: @@ -2037,6 +2071,44 @@ port_speed!N/A return ('', '') + def _cmd_chrcrelationship(self, **kwargs): + if 'obj' not in kwargs: + return self._errors['CMMVC5707E'] + id_num = kwargs['obj'] + + try: + rcrel = self._rcrelationship_list[id_num] + except KeyError: + return self._errors['CMMVC5753E'] + + nonull_num = 0 + masterchange = None + if 'masterchange' in kwargs: + masterchange = kwargs['masterchange'].strip('\'\"') + nonull_num += 1 + + auxchange = None + if 'auxchange' in kwargs: + auxchange = kwargs['auxchange'].strip('\'\"') + nonull_num += 1 + + cycleperiodseconds = None + if 'cycleperiodseconds' in kwargs: + cycleperiodseconds = kwargs['cycleperiodseconds'].strip('\'\"') + nonull_num += 1 + + if nonull_num > 1: + return self._errors['CMMVC5713E'] + elif masterchange: + rcrel['master_change_vdisk_name'] = masterchange + return ('', '') + elif auxchange: + rcrel['aux_change_vdisk_name'] = auxchange + return ('', '') + elif cycleperiodseconds: + rcrel['cycle_period_seconds'] = cycleperiodseconds + return ('', '') + def _rc_state_transition(self, function, rcrel): if (function == 'wait' and 'wait' not in self._rc_transitions[rcrel['state']]): @@ -2045,6 +2117,10 @@ port_speed!N/A if rcrel['state'] == 'inconsistent_copying' and function == 'wait': if rcrel['progress'] == '0': rcrel['progress'] = '50' + elif (storwize_const.GMCV_MULTI == rcrel['cycling_mode'] + and storwize_const.GLOBAL == rcrel['copy_type']): + rcrel['progress'] = '100' + rcrel['state'] = 'consistent_copying' else: rcrel['progress'] = '100' rcrel['state'] = 'consistent_synchronized' @@ -3794,7 +3870,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): 'replication': False, 'stretched_cluster': None, 'nofmtdisk': False, - 'mirror_pool': None} + 'mirror_pool': None, + 'cycle_period_seconds': 300, + } return opt @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_qos') @@ -4389,7 +4467,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.replications[rep_type] = ( self.driver.replication_factory(rep_type, fake_target)) volume = self._create_volume() - volume['replication_status'] = 'enabled' + volume['replication_status'] = fields.ReplicationStatus.ENABLED fake_target_vol = 'vol-target-id' get_relationship.return_value = {'aux_vdisk_name': fake_target_vol} with mock.patch.object( @@ -4410,7 +4488,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): def _storwize_svc_extend_volume_replication_failover(self): volume = self._create_volume() - volume['replication_status'] = 'failed-over' + volume['replication_status'] = fields.ReplicationStatus.FAILED_OVER with mock.patch.object( self.driver, '_get_volume_replicated_type_mirror') as mirror_type: @@ -4834,151 +4912,6 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.delete_volume(volume) self.assertNotIn(volume['id'], self.driver._vdiskcopyops) - def test_storwize_create_volume_with_replication_disable(self): - volume = self._generate_vol_info() - - model_update = self.driver.create_volume(volume) - self.assertIsNone(model_update) - - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertIsNone(model_update) - - def test_storwize_create_volume_with_strech_cluster_replication(self): - # Set replication flag, set pool openstack2 for secondary volume. - self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2') - - # Create a type for repliation. - volume = self._generate_vol_info() - volume_type = self._create_replication_volume_type(True) - volume['volume_type_id'] = volume_type['id'] - - self.driver.do_setup(self.ctxt) - - model_update = self.driver.create_volume(volume) - self.assertEqual('copying', model_update['replication_status']) - - volume['replication_status'] = 'copying' - volume['replication_extended_status'] = None - - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('copying', model_update['replication_status']) - - # Primary copy offline, secondary copy online, data consistent - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'offline') - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('active-stop', model_update['replication_status']) - - # Primary copy offline, secondary copy online, data inconsistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'No', - copy="secondary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('error', model_update['replication_status']) - - # Primary copy online, secondary copy offline, data consistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'yes', - copy="secondary") - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'offline', - copy="secondary") - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'online') - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('error', model_update['replication_status']) - - # Primary copy online, secondary copy offline, data inconsistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'no', - copy="secondary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('error', model_update['replication_status']) - - # Primary copy offline, secondary copy offline, data consistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'yes', - copy="secondary") - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'offline', - copy="primary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('error', model_update['replication_status']) - - # Primary copy offline, secondary copy offline, data inconsistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'no', - copy="secondary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('error', model_update['replication_status']) - - # Primary copy online, secondary copy online, data inconsistent - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'online', - copy="secondary") - self.sim.change_vdiskcopy_attr(volume['name'], 'status', 'online', - copy="primary") - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'no', - copy="secondary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('copying', model_update['replication_status']) - - # Primary copy online, secondary copy online, data consistent - self.sim.change_vdiskcopy_attr(volume['name'], 'sync', 'yes', - copy="secondary") - model_update = self.driver.get_replication_status(self.ctxt, volume) - self.assertEqual('active', model_update['replication_status']) - - # Check the volume copy created on pool openstack2. - attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) - self.assertIn('openstack2', attrs['mdisk_grp_name']) - - primary_status = attrs['primary'] - self.driver.promote_replica(self.ctxt, volume) - - # After promote_replica, primary copy should be swiched. - attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) - self.assertEqual(primary_status[0], attrs['primary'][1]) - self.assertEqual(primary_status[1], attrs['primary'][0]) - - self.driver.delete_volume(volume) - attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) - self.assertIsNone(attrs) - - def test_storwize_create_cloned_volume_with_strech_cluster_replica(self): - # Set replication flag, set pool openstack2 for secondary volume. - self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2') - self.driver.do_setup(self.ctxt) - - # Create a source volume. - src_volume = self._generate_vol_info() - self.driver.create_volume(src_volume) - - # Create a type for repliation. - volume = self._generate_vol_info() - volume_type = self._create_replication_volume_type(True) - volume['volume_type_id'] = volume_type['id'] - - # Create a cloned volume from source volume. - model_update = self.driver.create_cloned_volume(volume, src_volume) - self.assertEqual('copying', model_update['replication_status']) - - # Check the replication volume created on pool openstack2. - attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) - self.assertIn('openstack2', attrs['mdisk_grp_name']) - - def test_storwize_create_snapshot_volume_with_strech_cluster_replica(self): - # Set replication flag, set pool openstack2 for secondary volume. - self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2') - self.driver.do_setup(self.ctxt) - - vol1 = self._create_volume() - snap = self._generate_snap_info(vol1.id) - self.driver.create_snapshot(snap) - vol2 = self._generate_vol_info() - - # Create a type for repliation. - vol2 = self._generate_vol_info() - volume_type = self._create_replication_volume_type(True) - vol2['volume_type_id'] = volume_type['id'] - - model_update = self.driver.create_volume_from_snapshot(vol2, snap) - self._assert_vol_exists(vol2['name'], True) - self.assertEqual('copying', model_update['replication_status']) - # Check the replication volume created on pool openstack2. - attrs = self.driver._helpers.get_vdisk_attributes(vol2['name']) - self.assertIn('openstack2', attrs['mdisk_grp_name']) - # Test groups operation #### @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') def test_storwize_group_create_with_replication( @@ -6210,13 +6143,25 @@ class StorwizeSVCReplicationTestCase(test.TestCase): def _create_replica_volume_type(self, enable, rep_type=storwize_const.METRO, - opts=None, vol_type_name=None): + opts=None, vol_type_name=None, + cycle_period_seconds=None): # Generate a volume type for volume repliation. if enable: if rep_type == storwize_const.METRO: spec = {'replication_enabled': ' True', 'replication_type': ' metro'} type_name = 'rep_metro' + elif rep_type == storwize_const.GMCV: + if cycle_period_seconds: + spec = {'replication_enabled': ' True', + 'replication_type': ' gmcv', + 'drivers:cycle_period_seconds': + cycle_period_seconds} + type_name = 'rep_gmcv_with_cps' + cycle_period_seconds + else: + spec = {'replication_enabled': ' True', + 'replication_type': ' gmcv'} + type_name = 'rep_gmcv_default' else: spec = {'replication_enabled': ' True', 'replication_type': ' global'} @@ -6238,6 +6183,14 @@ class StorwizeSVCReplicationTestCase(test.TestCase): True, rep_type=storwize_const.METRO) self.gm_type = self._create_replica_volume_type( True, rep_type=storwize_const.GLOBAL) + self.gmcv_default_type = self._create_replica_volume_type( + True, rep_type=storwize_const.GMCV) + self.gmcv_with_cps600_type = self._create_replica_volume_type( + True, rep_type=storwize_const.GMCV, cycle_period_seconds="600") + self.gmcv_with_cps900_type = self._create_replica_volume_type( + True, rep_type=storwize_const.GMCV, cycle_period_seconds="900") + self.gmcv_with_cps86401_type = self._create_replica_volume_type( + True, rep_type=storwize_const.GMCV, cycle_period_seconds="86401") self.non_replica_type = self._create_replica_volume_type(False) def _create_test_volume(self, rep_type): @@ -6328,44 +6281,146 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(volume) self.driver.delete_volume(volume) self._validate_replic_vol_deletion(volume) # Create global mirror replication. volume, model_update = self._create_test_volume(self.gm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(volume) self.driver.delete_volume(volume) self._validate_replic_vol_deletion(volume) - def _validate_replic_vol_creation(self, volume): - # Create metro mirror volume + # Create global mirror with change volumes replication. + volume, model_update = self._create_test_volume( + self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + self.driver.delete_volume(volume) + self._validate_replic_vol_deletion(volume, True) + # gmcv with specified cycle_period_seconds + volume, model_update = self._create_test_volume( + self.gmcv_with_cps600_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + self.driver.delete_volume(volume) + self._validate_replic_vol_deletion(volume, True) + # gmcv with invalid cycle_period_seconds + self.assertRaises(exception.InvalidInput, + self._create_test_volume, + self.gmcv_with_cps86401_type) + + def _validate_replic_vol_creation(self, volume, isGMCV=False): self._assert_vol_exists(volume['name'], True) self._assert_vol_exists( storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], True) + if isGMCV: + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'], True) + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], True) + + rel_info = self.driver._helpers.get_relationship_info(volume['name']) + self.assertIsNotNone(rel_info) + if isGMCV: + vol_rep_type = rel_info['copy_type'] + cycling_mode = rel_info['cycling_mode'] + cycle_period_seconds = rel_info['cycle_period_seconds'] + rep_type = self.driver._get_volume_replicated_type( + self.ctxt, volume) + src_opts = self.driver._get_vdisk_params(volume['volume_type_id']) + opt_cycle_period_seconds = six.text_type( + src_opts.get('cycle_period_seconds')) + self.assertEqual(opt_cycle_period_seconds, cycle_period_seconds) + self.assertEqual(storwize_const.GMCV_MULTI, cycling_mode) + self.assertEqual(storwize_const.GLOBAL, vol_rep_type) + self.assertEqual(storwize_const.GMCV, rep_type) + self.assertEqual('master', rel_info['primary']) + self.assertEqual(volume['name'], rel_info['master_vdisk_name']) + self.assertEqual( + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], + rel_info['aux_vdisk_name']) + self.assertEqual('inconsistent_copying', rel_info['state']) + self.assertEqual( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'], + rel_info['master_change_vdisk_name']) + self.assertEqual( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], + rel_info['aux_change_vdisk_name']) + self.assertEqual('inconsistent_copying', rel_info['state']) + self.sim._rc_state_transition('wait', rel_info) + self.assertEqual('consistent_copying', rel_info['state']) + else: + vol_rep_type = rel_info['copy_type'] + rep_type = self.driver._get_volume_replicated_type( + self.ctxt, volume) + self.assertEqual(rep_type, vol_rep_type) + + self.assertEqual('master', rel_info['primary']) + self.assertEqual(volume['name'], rel_info['master_vdisk_name']) + self.assertEqual( + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], + rel_info['aux_vdisk_name']) + self.assertEqual('inconsistent_copying', rel_info['state']) + + self.sim._rc_state_transition('wait', rel_info) + self.assertEqual('consistent_synchronized', rel_info['state']) + + def _validate_gmcv_vol_retype(self, volume): + self._assert_vol_exists(volume['name'], True) + self._assert_vol_exists( + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], True) + self._assert_vol_exists(storwize_const.REPLICA_CHG_VOL_PREFIX + + volume['name'], True) + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], True) rel_info = self.driver._helpers.get_relationship_info(volume['name']) self.assertIsNotNone(rel_info) - vol_rep_type = rel_info['copy_type'] - rep_type = self.driver._get_volume_replicated_type(self.ctxt, volume) - self.assertEqual(vol_rep_type, rep_type) - self.assertEqual(rel_info['primary'], 'master') - self.assertEqual(rel_info['master_vdisk_name'], volume['name']) - self.assertEqual( - rel_info['aux_vdisk_name'], - storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name']) - self.assertEqual(rel_info['state'], 'inconsistent_copying') + src_opts = self.driver._get_vdisk_params(volume['volume_type_id']) + opt_cycle_period_seconds = six.text_type( + src_opts.get('cycle_period_seconds')) + self.assertEqual(opt_cycle_period_seconds, + rel_info['cycle_period_seconds']) + self.assertEqual(storwize_const.GMCV_MULTI, rel_info['cycling_mode']) + self.assertEqual(storwize_const.GLOBAL, rel_info['copy_type']) + self.assertEqual(storwize_const.GMCV, + self.driver._get_volume_replicated_type( + self.ctxt, volume)) + self.assertEqual('master', rel_info['primary']) + self.assertEqual(volume['name'], rel_info['master_vdisk_name']) + self.assertEqual((storwize_const.REPLICA_CHG_VOL_PREFIX + + volume['name']), + rel_info['master_change_vdisk_name']) + aux_vdisk_name = (storwize_const.REPLICA_AUX_VOL_PREFIX + + volume['name']) + self.assertEqual(aux_vdisk_name, + rel_info['aux_vdisk_name']) + self.assertEqual((storwize_const.REPLICA_CHG_VOL_PREFIX + + aux_vdisk_name), + rel_info['aux_change_vdisk_name']) - self.sim._rc_state_transition('wait', rel_info) - self.assertEqual(rel_info['state'], 'consistent_synchronized') - - def _validate_replic_vol_deletion(self, volume): + def _validate_replic_vol_deletion(self, volume, isGMCV=False): self._assert_vol_exists(volume['name'], False) self._assert_vol_exists( storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) + if isGMCV: + # All change volumes should be deleted + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'], False) + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) rel_info = self.driver._helpers.get_relationship_info(volume['name']) self.assertIsNone(rel_info) @@ -6377,20 +6432,44 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication volume. vol1, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) snap = testutils.create_snapshot(self.ctxt, vol1.id) self.driver.create_snapshot(snap) vol2 = self._generate_vol_info(self.mm_type) model_update = self.driver.create_volume_from_snapshot(vol2, snap) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(vol2) self.driver.delete_snapshot(snap) self.driver.delete_volume(vol1) self.driver.delete_volume(vol2) + # Create gmcv replication volume. + vol1, model_update = self._create_test_volume(self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(vol1, True) + snap = testutils.create_snapshot(self.ctxt, vol1.id) + self.assertRaises(exception.VolumeDriverException, + self.driver.create_snapshot, + snap) + self.driver.delete_volume(vol1) + + # gmcv with specified cycle_period_seconds + vol1, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(vol1, True) + snap = testutils.create_snapshot(self.ctxt, vol1.id) + self.assertRaises(exception.VolumeDriverException, + self.driver.create_snapshot, snap) + self.driver.delete_volume(vol1) + def test_storwize_create_cloned_volume_with_mirror_replica(self): # Set replication target self.driver.configuration.set_override('replication_device', @@ -6399,17 +6478,51 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create a source metro mirror replication volume. src_volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) volume = self._generate_vol_info(self.mm_type) # Create a cloned volume from source volume. model_update = self.driver.create_cloned_volume(volume, src_volume) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(volume) self.driver.delete_volume(src_volume) self.driver.delete_volume(volume) + # Create a source gmcv replication volume. + src_volume, model_update = self._create_test_volume( + self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + volume = self._generate_vol_info(self.gmcv_default_type) + + # Create a cloned volume from source volume. + model_update = self.driver.create_cloned_volume(volume, src_volume) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + + self.driver.delete_volume(src_volume) + self.driver.delete_volume(volume) + + # Create a source gmcv volume with specified cycle_period_seconds + src_volume, model_update = self._create_test_volume( + self.gmcv_with_cps600_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + volume = self._generate_vol_info(self.gmcv_with_cps600_type) + + # Create a cloned volume from source volume. + model_update = self.driver.create_cloned_volume(volume, src_volume) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + + self.driver.delete_volume(src_volume) + self.driver.delete_volume(volume) @ddt.data(({'replication_enabled': ' True', 'replication_type': ' global'}, @@ -6456,14 +6569,30 @@ class StorwizeSVCReplicationTestCase(test.TestCase): host = {'host': 'openstack@svc#openstack'} volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.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 from mm to gm + self.assertRaises(exception.VolumeDriverException, + self.driver.retype, self.ctxt, + volume, self.gm_type, diff, host) + + # Retype from mm to gmcv + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.mm_type['id'], self.gmcv_with_cps600_type['id']) + self.assertRaises(exception.VolumeDriverException, + self.driver.retype, self.ctxt, + volume, self.gmcv_with_cps600_type, diff, host) diff, _equal = volume_types.volume_types_diff( self.ctxt, self.non_replica_type['id'], self.mm_type['id']) - # Disable replica + # Retype from mm to non-replica retyped, model_update = self.driver.retype( self.ctxt, volume, self.non_replica_type, diff, host) - self.assertEqual('disabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.DISABLED, + model_update['replication_status']) self._assert_vol_exists( storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) @@ -6472,38 +6601,147 @@ class StorwizeSVCReplicationTestCase(test.TestCase): rel_info = self.driver._helpers.get_relationship_info(volume['name']) self.assertIsNone(rel_info) + # Create gmcv volume + volume, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + # Retype from gmcv to gm + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.gmcv_with_cps900_type['id'], self.gm_type['id']) + self.assertRaises(exception.VolumeDriverException, + self.driver.retype, self.ctxt, + volume, self.gm_type, diff, host) + # Retype from gmcv to non-replica + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.gmcv_with_cps900_type['id'], + self.non_replica_type['id']) + retyped, model_update = self.driver.retype( + self.ctxt, volume, self.non_replica_type, diff, host) + self.assertEqual(fields.ReplicationStatus.DISABLED, + model_update['replication_status']) + # All change volumes should be deleted + self._assert_vol_exists( + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'], False) + self._assert_vol_exists( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) + + self.driver.delete_volume(volume) + self._assert_vol_exists(volume['name'], False) + rel_info = self.driver._helpers.get_relationship_info(volume['name']) + self.assertIsNone(rel_info) + def test_storwize_retype_from_none_to_mirror_replication(self): # Set replication target self.driver.configuration.set_override('replication_device', [self.rep_target]) self.driver.do_setup(self.ctxt) - host = {'host': 'openstack@svc#openstack'} - - diff, _equal = volume_types.volume_types_diff( - self.ctxt, self.non_replica_type['id'], self.mm_type['id']) volume, model_update = self._create_test_volume(self.non_replica_type) self.assertIsNone(model_update) - # Enable replica + # Retype to mm replica + host = {'host': 'openstack@svc#openstack'} + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.non_replica_type['id'], self.mm_type['id']) retyped, model_update = self.driver.retype( self.ctxt, volume, self.mm_type, diff, host) volume['volume_type_id'] = self.mm_type['id'] volume['volume_type'] = self.mm_type - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(volume) self.driver.delete_volume(volume) + # Create non-replica volume + volume, model_update = self._create_test_volume(self.non_replica_type) + self.assertIsNone(model_update) + + # Retype to gmcv replica + host = {'host': 'openstack@svc#openstack'} + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.non_replica_type['id'], + self.gmcv_with_cps900_type['id']) + retyped, model_update = self.driver.retype( + self.ctxt, volume, self.gmcv_with_cps900_type, diff, host) + volume['volume_type_id'] = self.gmcv_with_cps900_type['id'] + volume['volume_type'] = self.gmcv_with_cps900_type + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + + self.driver.delete_volume(volume) + self._validate_replic_vol_deletion(volume, True) + + def test_storwize_retype_from_gmcv_to_gmcv_replication(self): + # Set replication target + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + self.driver.do_setup(self.ctxt) + + # Create gmcv default volume + volume, model_update = self._create_test_volume(self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(volume, True) + + # Retype to gmcv with cycle_period_seconds 600 replica + host = {'host': 'openstack@svc#openstack'} + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.gmcv_default_type['id'], + self.gmcv_with_cps600_type['id']) + self.driver.retype(self.ctxt, volume, + self.gmcv_with_cps600_type, diff, host) + volume['volume_type_id'] = self.gmcv_with_cps600_type['id'] + volume['volume_type'] = self.gmcv_with_cps600_type + self._validate_gmcv_vol_retype(volume) + + # Retype to gmcv with cycle_period_seconds 900 replica + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.gmcv_with_cps600_type['id'], + self.gmcv_with_cps900_type['id']) + self.driver.retype(self.ctxt, volume, + self.gmcv_with_cps900_type, diff, host) + volume['volume_type_id'] = self.gmcv_with_cps900_type['id'] + volume['volume_type'] = self.gmcv_with_cps900_type + self._validate_gmcv_vol_retype(volume) + + # Retype to gmcv with invalid cycle_period_seconds + diff, _equal = volume_types.volume_types_diff( + self.ctxt, self.gmcv_with_cps600_type['id'], + self.gmcv_with_cps86401_type['id']) + self.assertRaises(exception.InvalidInput, self.driver.retype, + self.ctxt, volume, self.gmcv_with_cps86401_type, + diff, host) + + # Retype to gmcv default volume + diff, _equal = volume_types.volume_types_diff( + self.ctxt, + self.gmcv_with_cps900_type['id'], + self.gmcv_default_type['id']) + self.driver.retype(self.ctxt, volume, + self.gmcv_default_type, diff, host) + volume['volume_type_id'] = self.gmcv_default_type['id'] + volume['volume_type'] = self.gmcv_default_type + self._validate_gmcv_vol_retype(volume) + + self.driver.delete_volume(volume) + self._validate_replic_vol_deletion(volume, True) + def test_storwize_extend_volume_replication(self): # Set replication target. self.driver.configuration.set_override('replication_device', [self.rep_target]) self.driver.do_setup(self.ctxt) - # Create metro mirror replication. + # Create metro mirror replication volume. volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self.driver.extend_volume(volume, '13') attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) @@ -6518,15 +6756,47 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.delete_volume(volume) self._validate_replic_vol_deletion(volume) + # Create gmcv replication volume. + volume, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + self.driver.extend_volume(volume, 15) + attrs = self.driver._helpers.get_vdisk_attributes(volume['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 15) + + attrs = self.driver._aux_backend_helpers.get_vdisk_attributes( + storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 15) + + attrs = self.driver._aux_backend_helpers.get_vdisk_attributes( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + + volume['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 15) + + attrs = self.driver._helpers.get_vdisk_attributes( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 15) + + self.driver.delete_volume(volume) + self._validate_replic_vol_deletion(volume) + def test_storwize_manage_existing_mismatch_with_volume_replication(self): # Set replication target. self.driver.configuration.set_override('replication_device', [self.rep_target]) self.driver.do_setup(self.ctxt) - # Create replication volume. + # Create mm replication volume. rep_volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) # Create non-replication volume. non_rep_volume, model_update = self._create_test_volume( @@ -6551,6 +6821,31 @@ class StorwizeSVCReplicationTestCase(test.TestCase): new_volume['volume_type'] = self.gm_type self.assertRaises(exception.ManageExistingVolumeTypeMismatch, self.driver.manage_existing, new_volume, ref) + + ref = {'source-name': rep_volume['name']} + new_volume['volume_type_id'] = self.gmcv_with_cps900_type['id'] + new_volume['volume_type'] = self.gmcv_with_cps900_type + self.assertRaises(exception.ManageExistingVolumeTypeMismatch, + self.driver.manage_existing, new_volume, ref) + + self.driver.delete_volume(rep_volume) + self.driver.delete_volume(new_volume) + + # Create gmcv default replication volume + rep_volume, model_update = self._create_test_volume( + self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + new_volume = self._generate_vol_info() + ref = {'source-name': rep_volume['name']} + new_volume['volume_type_id'] = self.gmcv_with_cps900_type['id'] + new_volume['volume_type'] = self.gmcv_with_cps900_type + # manage existing gmcv volume with different cycle period seconds + self.assertRaises( + exception.ManageExistingVolumeTypeMismatch, + self.driver.manage_existing, + new_volume, + ref) self.driver.delete_volume(rep_volume) self.driver.delete_volume(new_volume) @@ -6560,9 +6855,10 @@ class StorwizeSVCReplicationTestCase(test.TestCase): [self.rep_target]) self.driver.do_setup(self.ctxt) - # Create replication volume. + # Create mm replication volume. rep_volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) uid_of_master = self._get_vdisk_uid(rep_volume['name']) uid_of_aux = self._get_vdisk_uid( @@ -6582,6 +6878,49 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.assertEqual(uid_of_aux, uid_of_aux_volume) self.driver.delete_volume(rep_volume) + # Create gmcv replication volume. + rep_volume, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + uid_of_master = self._get_vdisk_uid(rep_volume['name']) + uid_of_master_change = self._get_vdisk_uid( + storwize_const.REPLICA_CHG_VOL_PREFIX + + rep_volume['name']) + uid_of_aux = self._get_vdisk_uid( + storwize_const.REPLICA_AUX_VOL_PREFIX + + rep_volume['name']) + uid_of_aux_change = self._get_vdisk_uid( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + + rep_volume['name']) + + new_volume = self._generate_vol_info() + ref = {'source-name': rep_volume['name']} + new_volume['volume_type_id'] = self.gmcv_with_cps900_type['id'] + new_volume['volume_type'] = self.gmcv_with_cps900_type + self.driver.manage_existing(new_volume, ref) + + # Check the uid of the volume which has been renamed. + uid_of_new_master = self._get_vdisk_uid(new_volume['name']) + uid_of_new_master_change = self._get_vdisk_uid( + storwize_const.REPLICA_CHG_VOL_PREFIX + + new_volume['name']) + uid_of_new_aux = self._get_vdisk_uid( + storwize_const.REPLICA_AUX_VOL_PREFIX + + new_volume['name']) + uid_of_new_aux_change = self._get_vdisk_uid( + storwize_const.REPLICA_CHG_VOL_PREFIX + + storwize_const.REPLICA_AUX_VOL_PREFIX + + new_volume['name']) + + self.assertEqual(uid_of_master, uid_of_new_master) + self.assertEqual(uid_of_aux, uid_of_new_aux) + self.assertEqual(uid_of_master_change, uid_of_new_master_change) + self.assertEqual(uid_of_aux_change, uid_of_new_aux_change) + + self.driver.delete_volume(rep_volume) @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'rename_vdisk') @mock.patch.object(storwize_svc_common.StorwizeHelpers, @@ -6618,18 +6957,30 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. volume, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) self._validate_replic_vol_creation(volume) # Delete volume in non-failover state self.driver.delete_volume(volume) self._validate_replic_vol_deletion(volume) + # Create gmcv replication. + gmcv_volume, model_update = self._create_test_volume( + self.gmcv_with_cps600_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + self._validate_replic_vol_creation(gmcv_volume, True) + + # Delete gmcv volume in non-failover state + self.driver.delete_volume(gmcv_volume) + self._validate_replic_vol_deletion(gmcv_volume, True) + non_replica_vol, model_update = self._create_test_volume( self.non_replica_type) self.assertIsNone(model_update) - volumes = [volume, non_replica_vol] + volumes = [volume, non_replica_vol, gmcv_volume] # Delete volume in failover state self.driver.failover_host( self.ctxt, volumes, self.rep_target['backend_id']) @@ -6641,6 +6992,10 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Delete replicate volume in failover state self.driver.delete_volume(volume) self._validate_replic_vol_deletion(volume) + + self.driver.delete_volume(gmcv_volume) + self._validate_replic_vol_deletion(gmcv_volume, True) + self.driver.failover_host( self.ctxt, volumes, 'default') self.driver.delete_volume(non_replica_vol) @@ -6665,7 +7020,12 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver._helpers.delete_rc_volume(fake_name) get_relationship_info.assert_called_once_with(fake_name) delete_relationship.assert_called_once_with(fake_name) - delete_vdisk.assert_called_once_with(fake_name, False) + master_change_fake_name = ( + storwize_const.REPLICA_CHG_VOL_PREFIX + fake_name) + calls = [mock.call(master_change_fake_name, False), + mock.call(fake_name, False)] + delete_vdisk.assert_has_calls(calls, any_order=True) + self.assertEqual(2, delete_vdisk.call_count) @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'delete_vdisk') @@ -6714,9 +7074,16 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. mm_vol, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) - volumes = [mm_vol] + # Create gmcv replication. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + volumes = [mm_vol, gmcv_vol] self.driver._replica_enabled = False self.assertRaises(exception.UnableToFailOver, @@ -6742,27 +7109,41 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.failover_host, self.ctxt, volumes, 'default') self.driver.delete_volume(mm_vol) + self.driver.delete_volume(gmcv_vol) @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info') def test_failover_volume_relationship_error(self, get_relationship_info): # Create global mirror replication. gm_vol, model_update = self._create_test_volume(self.gm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create gmcv replication. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) get_relationship_info.side_effect = [None, + exception.VolumeDriverException, + None, exception.VolumeDriverException] expected_list = [{'updates': {'replication_status': - 'error_failing-over', + fields.ReplicationStatus.FAILOVER_ERROR, 'status': 'error'}, - 'volume_id': gm_vol['id']} + 'volume_id': gm_vol['id']}, + {'updates': {'replication_status': + fields.ReplicationStatus.FAILOVER_ERROR, + 'status': 'error'}, + 'volume_id': gmcv_vol['id']} ] - volumes_update = self.driver._failover_replica_volumes(self.ctxt, - [gm_vol]) + volumes_update = self.driver._failover_replica_volumes( + self.ctxt, [gm_vol, gmcv_vol]) self.assertEqual(expected_list, volumes_update) - volumes_update = self.driver._failover_replica_volumes(self.ctxt, - [gm_vol]) + volumes_update = self.driver._failover_replica_volumes( + self.ctxt, [gm_vol, gmcv_vol]) self.assertEqual(expected_list, volumes_update) @mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver, @@ -6778,18 +7159,31 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. mm_vol, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) # Create global replication volume. gm_vol, model_update = self._create_test_volume(self.gm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) - volumes = [mm_vol, gm_vol] - expected_list = [{'updates': {'replication_status': 'failed-over'}, - 'volume_id': mm_vol['id']}, - {'updates': {'replication_status': 'failed-over'}, - 'volume_id': gm_vol['id']} - ] + # Create gmcv volume. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_with_cps600_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + volumes = [mm_vol, gm_vol, gmcv_vol] + expected_list = [ + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': mm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': gm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': gmcv_vol['id']}] target_id, volume_list = self.driver.failover_host( self.ctxt, volumes, self.rep_target['backend_id']) @@ -6806,6 +7200,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.delete_volume(mm_vol) self.driver.delete_volume(gm_vol) + self.driver.delete_volume(gmcv_vol) target_id, volume_list = self.driver.failover_host( self.ctxt, volumes, None) @@ -6825,24 +7220,37 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. mm_vol, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) mm_vol['status'] = 'in-use' + # Create gmcv replication. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_with_cps600_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + gmcv_vol['status'] = 'in-use' + # Create non-replication volume. non_replica_vol, model_update = self._create_test_volume( self.non_replica_type) self.assertIsNone(model_update) non_replica_vol['status'] = 'error' - volumes = [mm_vol, non_replica_vol] + volumes = [mm_vol, gmcv_vol, non_replica_vol] rep_data1 = json.dumps({'previous_status': mm_vol['status']}) - rep_data2 = json.dumps({'previous_status': non_replica_vol['status']}) + rep_data2 = json.dumps({'previous_status': gmcv_vol['status']}) + rep_data3 = json.dumps({'previous_status': non_replica_vol['status']}) + expected_list = [{'updates': {'status': 'error', 'replication_driver_data': rep_data1}, 'volume_id': mm_vol['id']}, {'updates': {'status': 'error', 'replication_driver_data': rep_data2}, + 'volume_id': gmcv_vol['id']}, + {'updates': {'status': 'error', + 'replication_driver_data': rep_data3}, 'volume_id': non_replica_vol['id']}, ] @@ -6869,6 +7277,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): non_replica_vol) self.driver.failover_host(self.ctxt, volumes, 'default') self.driver.delete_volume(mm_vol) + self.driver.delete_volume(gmcv_vol) self.driver.delete_volume(non_replica_vol) @mock.patch.object(storwize_svc_common.StorwizeHelpers, @@ -6907,26 +7316,45 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create metro mirror replication. mm_vol, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) # Create global mirror replication. gm_vol, model_update = self._create_test_volume(self.gm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) - volumes = [gm_vol, mm_vol] - failover_expect = [{'updates': {'replication_status': 'failed-over'}, - 'volume_id': gm_vol['id']}, - {'updates': {'replication_status': 'failed-over'}, - 'volume_id': mm_vol['id']} - ] + # Create gmcv replication. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_with_cps900_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) - failback_expect = [{'updates': {'replication_status': 'enabled', - 'status': 'available'}, - 'volume_id': gm_vol['id']}, - {'updates': {'replication_status': 'enabled', - 'status': 'available'}, - 'volume_id': mm_vol['id']}, - ] + volumes = [gm_vol, mm_vol, gmcv_vol] + failover_expect = [ + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': gm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': mm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': gmcv_vol['id']}] + + failback_expect = [ + {'updates': + {'replication_status': fields.ReplicationStatus.ENABLED, + 'status': 'available'}, + 'volume_id': gm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.ENABLED, + 'status': 'available'}, + 'volume_id': mm_vol['id']}, + {'updates': + {'replication_status': fields.ReplicationStatus.ENABLED, + 'status': 'available'}, + 'volume_id': gmcv_vol['id']}] # Already failback target_id, volume_list = self.driver.failover_host( self.ctxt, volumes, 'default') @@ -6952,6 +7380,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.assertTrue(update_volume_stats.called) self.driver.delete_volume(mm_vol) self.driver.delete_volume(gm_vol) + self.driver.delete_volume(gmcv_vol) @mock.patch.object(storwize_svc_common.StorwizeSVCCommonDriver, '_update_volume_stats') @@ -6967,12 +7396,21 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # Create replication volume. mm_vol, model_update = self._create_test_volume(self.mm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) mm_vol['status'] = 'in-use' gm_vol, model_update = self._create_test_volume(self.gm_type) - self.assertEqual('enabled', model_update['replication_status']) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) gm_vol['status'] = 'available' + # Create gmcv replication. + gmcv_vol, model_update = self._create_test_volume( + self.gmcv_default_type) + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + gmcv_vol['status'] = 'in-use' + # Create non-replication volume. non_replica_vol1, model_update = self._create_test_volume( self.non_replica_type) @@ -6983,22 +7421,30 @@ class StorwizeSVCReplicationTestCase(test.TestCase): non_replica_vol1['status'] = 'error' non_replica_vol2['status'] = 'available' - volumes = [mm_vol, non_replica_vol1, non_replica_vol2, gm_vol] + volumes = [mm_vol, gmcv_vol, non_replica_vol1, + non_replica_vol2, gm_vol] rep_data0 = json.dumps({'previous_status': mm_vol['status']}) - rep_data1 = json.dumps({'previous_status': non_replica_vol1['status']}) - rep_data2 = json.dumps({'previous_status': non_replica_vol2['status']}) - failover_expect = [{'updates': {'replication_status': 'failed-over'}, - 'volume_id': gm_vol['id']}, - {'updates': {'status': 'error', - 'replication_driver_data': rep_data0}, - 'volume_id': mm_vol['id']}, - {'updates': {'status': 'error', - 'replication_driver_data': rep_data1}, - 'volume_id': non_replica_vol1['id']}, - {'updates': {'status': 'error', - 'replication_driver_data': rep_data2}, - 'volume_id': non_replica_vol2['id']}] + rep_data1 = json.dumps({'previous_status': gmcv_vol['status']}) + rep_data2 = json.dumps({'previous_status': non_replica_vol1['status']}) + rep_data3 = json.dumps({'previous_status': non_replica_vol2['status']}) + failover_expect = [ + {'updates': + {'replication_status': fields.ReplicationStatus.FAILED_OVER}, + 'volume_id': gm_vol['id']}, + {'updates': {'status': 'error', + 'replication_driver_data': rep_data0}, + 'volume_id': mm_vol['id']}, + {'updates': {'status': 'error', + 'replication_driver_data': rep_data1}, + 'volume_id': gmcv_vol['id']}, + {'updates': {'status': 'error', + 'replication_driver_data': rep_data2}, + 'volume_id': non_replica_vol1['id']}, + {'updates': {'status': 'error', + 'replication_driver_data': rep_data3}, + 'volume_id': non_replica_vol2['id']}] + # Already failback target_id, volume_list = self.driver.failover_host( self.ctxt, volumes, 'default') @@ -7016,15 +7462,20 @@ class StorwizeSVCReplicationTestCase(test.TestCase): # fail back operation mm_vol['replication_driver_data'] = json.dumps( {'previous_status': 'in-use'}) + gmcv_vol['replication_driver_data'] = json.dumps( + {'previous_status': 'in-use'}) non_replica_vol1['replication_driver_data'] = json.dumps( {'previous_status': 'error'}) non_replica_vol2['replication_driver_data'] = json.dumps( {'previous_status': 'available'}) gm_vol['status'] = 'in-use' - rep_data3 = json.dumps({'previous_status': gm_vol['status']}) + rep_data4 = json.dumps({'previous_status': gm_vol['status']}) failback_expect = [{'updates': {'status': 'in-use', 'replication_driver_data': ''}, 'volume_id': mm_vol['id']}, + {'updates': {'status': 'in-use', + 'replication_driver_data': ''}, + 'volume_id': gmcv_vol['id']}, {'updates': {'status': 'error', 'replication_driver_data': ''}, 'volume_id': non_replica_vol1['id']}, @@ -7032,7 +7483,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): 'replication_driver_data': ''}, 'volume_id': non_replica_vol2['id']}, {'updates': {'status': 'error', - 'replication_driver_data': rep_data3}, + 'replication_driver_data': rep_data4}, 'volume_id': gm_vol['id']}] target_id, volume_list = self.driver.failover_host( self.ctxt, volumes, 'default') @@ -7043,6 +7494,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.assertTrue(update_storwize_state.called) self.assertTrue(update_volume_stats.called) self.driver.delete_volume(mm_vol) + self.driver.delete_volume(gmcv_vol) self.driver.delete_volume(non_replica_vol1) self.driver.delete_volume(non_replica_vol2) @@ -7111,20 +7563,29 @@ class StorwizeSVCReplicationTestCase(test.TestCase): mm_vol = self._generate_vol_info(self.mm_type) tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + mm_vol['name'] - volumes = [mm_vol] + # Create gmcv replication. + gmcv_vol = self._generate_vol_info(self.gmcv_with_cps600_type) + tgt_gmcv_volume = (storwize_const.REPLICA_AUX_VOL_PREFIX + + gmcv_vol['name']) + volumes = [mm_vol, gmcv_vol] + fake_info = {'volume': 'fake', 'master_vdisk_name': 'fake', 'aux_vdisk_name': 'fake'} sync_state = {'state': storwize_const.REP_CONSIS_SYNC, 'primary': 'fake'} sync_state.update(fake_info) + + sync_copying_state = {'state': storwize_const.REP_CONSIS_COPYING, + 'primary': 'fake'} + sync_copying_state.update(fake_info) + disconn_state = {'state': storwize_const.REP_IDL_DISC, 'primary': 'master'} disconn_state.update(fake_info) stop_state = {'state': storwize_const.REP_CONSIS_STOP, 'primary': 'aux'} stop_state.update(fake_info) - with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info', mock.Mock(return_value=None)): @@ -7137,19 +7598,31 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver._sync_with_aux(self.ctxt, volumes) self.assertFalse(start_relationship.called) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'get_relationship_info', + mock.Mock(return_value=sync_copying_state)): + self.driver._sync_with_aux(self.ctxt, volumes) + self.assertFalse(start_relationship.called) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info', mock.Mock(return_value=disconn_state)): self.driver._sync_with_aux(self.ctxt, volumes) - start_relationship.assert_called_once_with(tgt_volume) + calls = [mock.call(tgt_volume), mock.call(tgt_gmcv_volume)] + start_relationship.assert_has_calls(calls, any_order=True) + self.assertEqual(2, start_relationship.call_count) + start_relationship.reset_mock() with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info', mock.Mock(return_value=stop_state)): self.driver._sync_with_aux(self.ctxt, volumes) - start_relationship.assert_called_with(tgt_volume, - primary='aux') + calls = [mock.call(tgt_volume, primary='aux'), + mock.call(tgt_gmcv_volume, primary='aux')] + start_relationship.assert_has_calls(calls, any_order=True) + self.assertEqual(2, start_relationship.call_count) self.driver.delete_volume(mm_vol) + self.driver.delete_volume(gmcv_vol) @mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info') @@ -7158,12 +7631,18 @@ class StorwizeSVCReplicationTestCase(test.TestCase): def test_wait_replica_vol_ready(self, get_relationship_info): # Create metro mirror replication. mm_vol = self._generate_vol_info(self.mm_type) + + # Create gmcv replication. + gmcv_vol = self._generate_vol_info(self.gmcv_with_cps900_type) + fake_info = {'volume': 'fake', 'master_vdisk_name': 'fake', 'aux_vdisk_name': 'fake', 'primary': 'fake'} sync_state = {'state': storwize_const.REP_CONSIS_SYNC} sync_state.update(fake_info) + sync_copying_state = {'state': storwize_const.REP_CONSIS_COPYING} + sync_copying_state.update(fake_info) disconn_state = {'state': storwize_const.REP_IDL_DISC} disconn_state.update(fake_info) with mock.patch.object(storwize_svc_common.StorwizeHelpers, @@ -7173,14 +7652,33 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver._wait_replica_vol_ready, self.ctxt, mm_vol) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'get_relationship_info', + mock.Mock(return_value=None)): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver._wait_replica_vol_ready, + self.ctxt, gmcv_vol) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info', mock.Mock(return_value=sync_state)): self.driver._wait_replica_vol_ready(self.ctxt, mm_vol) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'get_relationship_info', + mock.Mock(return_value=sync_copying_state)): + self.driver._wait_replica_vol_ready(self.ctxt, gmcv_vol) + with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_relationship_info', mock.Mock(return_value=disconn_state)): self.assertRaises(exception.VolumeBackendAPIException, self.driver._wait_replica_vol_ready, self.ctxt, mm_vol) + + with mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'get_relationship_info', + mock.Mock(return_value=disconn_state)): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver._wait_replica_vol_ready, + self.ctxt, gmcv_vol) diff --git a/cinder/volume/drivers/ibm/storwize_svc/replication.py b/cinder/volume/drivers/ibm/storwize_svc/replication.py index dd9a3c3c2d0..faa3e20b426 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/replication.py +++ b/cinder/volume/drivers/ibm/storwize_svc/replication.py @@ -24,194 +24,30 @@ import six from cinder import exception from cinder.i18n import _ +from cinder.objects import fields from cinder import ssh_utils from cinder import utils from cinder.volume.drivers.ibm.storwize_svc import storwize_const -from cinder.volume import volume_types LOG = logging.getLogger(__name__) class StorwizeSVCReplication(object): - def __init__(self, driver): - self.driver = driver - - @staticmethod - def factory(driver): - """Use replication methods for the requested mode.""" - stretch = driver.configuration.storwize_svc_stretched_cluster_partner - if stretch: - return StorwizeSVCReplicationStretchedCluster(driver) - - def create_replica(self, ctxt, volume): - return (None, None) - - def is_replicated(self, volume): - return False - - def promote_replica(self, volume): - pass - - def test_replica(self, tgt_volume, src_volume): - pass - - def get_replication_status(self, volume): - return None - - def get_replication_info(self): - return {} - - def reenable_replication(self, volume): - """Enable the replication between the primary and secondary volumes. - - This is not implemented in the StorwizeSVCReplicationStretchedCluster, - as the Storwize backend is responsible for automatically resuming - mirroring when stopped. - """ - pass - - -class StorwizeSVCReplicationStretchedCluster(StorwizeSVCReplication): - """Support for Storwize/SVC stretched cluster mode replication. - - This stretched cluster mode implements volume replication in terms of - adding a copy to an existing volume, which changes a nonmirrored volume - into a mirrored volume. - """ - def __init__(self, driver, replication_target=None): - super(StorwizeSVCReplicationStretchedCluster, self).__init__(driver) + self.driver = driver self.target = replication_target or {} - def create_replica(self, ctxt, volume, vol_type = None): - # if vol_type is None, use the source volume type - if vol_type is None: - vol_type = volume['volume_type_id'] - vol_type = volume_types.get_volume_type(ctxt, vol_type) - conf = self.driver.configuration - dest_pool = conf.storwize_svc_stretched_cluster_partner + def failover_volume_host(self, context, vref): + pass - self.driver.add_vdisk_copy(volume['name'], dest_pool, vol_type) - vol_update = {'replication_status': 'copying'} - return vol_update + def replication_failback(self, volume): + pass - def delete_replica(self, volume): - vdisk = volume['name'] - copies = self.driver._helpers.get_vdisk_copies(vdisk) - secondary = copies['secondary'] - - if secondary: - self.driver._helpers.rm_vdisk_copy(volume['name'], - secondary['copy_id']) - else: - LOG.info('Could not find replica to delete of' - ' volume %(vol)s.', {'vol': vdisk}) - - def test_replica(self, tgt_volume, src_volume): - vdisk = src_volume['name'] - opts = self.driver._get_vdisk_params(tgt_volume['volume_type_id']) - copies = self.driver._helpers.get_vdisk_copies(vdisk) - - if copies['secondary']: - dest_pool = copies['secondary']['mdisk_grp_name'] - self.driver._helpers.create_copy(src_volume['name'], - tgt_volume['name'], - src_volume['id'], - self.driver.configuration, - opts, - True, - pool=dest_pool) - else: - msg = (_('Unable to create replica clone for volume %s.'), vdisk) - raise exception.VolumeDriverException(message=msg) - - def promote_replica(self, volume): - vdisk = volume['name'] - copies = self.driver._helpers.get_vdisk_copies(vdisk) - if copies['secondary']: - copy_id = copies['secondary']['copy_id'] - self.driver._helpers.change_vdisk_primary_copy(volume['name'], - copy_id) - else: - msg = (_('Unable to promote replica to primary for volume %s.' - ' No secondary copy available.'), - volume['id']) - raise exception.VolumeDriverException(message=msg) - - def get_replication_status(self, volume): - # Make sure volume is replicated, otherwise ignore - if volume['replication_status'] == 'disabled': - return None - - vdisk = volume['name'] - orig = (volume['replication_status'], - volume['replication_extended_status']) - copies = self.driver._helpers.get_vdisk_copies(vdisk) - - primary = copies.get('primary', None) - secondary = copies.get('secondary', None) - - # Check status of primary copy, set status 'error' as default - status = 'error' - if not primary: - primary = {'status': 'not found', - 'sync': 'no'} - else: - if primary['status'] == 'online': - status = 'active' - - extended1 = (_('Primary copy status: %(status)s' - ' and synchronized: %(sync)s.') % - {'status': primary['status'], - 'sync': primary['sync']}) - - # Check status of secondary copy - if not secondary: - secondary = {'status': 'not found', - 'sync': 'no', - 'sync_progress': '0'} - status = 'error' - else: - if secondary['status'] != 'online': - status = 'error' - else: - if secondary['sync'] == 'yes': - secondary['sync_progress'] = '100' - # Only change the status if not in error state - if status != 'error': - status = 'active' - else: - # Primary offline, secondary online, data consistent, - # stop copying - status = 'active-stop' - else: - # Primary and secondary both online, the status is copying - if status != 'error': - status = 'copying' - - extended2 = (_('Secondary copy status: %(status)s' - ' and synchronized: %(sync)s,' - ' sync progress is: %(progress)s%%.') % - {'status': secondary['status'], - 'sync': secondary['sync'], - 'progress': secondary['sync_progress']}) - - extended = '%s. %s' % (extended1, extended2) - - if (status, extended) != orig: - return {'replication_status': status, - 'replication_extended_status': extended} - else: - return None - - def get_replication_info(self): - data = {} - data['replication'] = True - return data + def volume_replication_setup(self, context, vref): + pass -class StorwizeSVCReplicationGlobalMirror( - StorwizeSVCReplicationStretchedCluster): +class StorwizeSVCReplicationGlobalMirror(StorwizeSVCReplication): """Support for Storwize/SVC global mirror mode replication. Global Mirror establishes a Global Mirror relationship between @@ -265,7 +101,8 @@ class StorwizeSVCReplicationGlobalMirror( rel_info = self.target_helpers.get_relationship_info(target_vol) # Reverse the role of the primary and secondary volumes self.target_helpers.switch_relationship(rel_info['name']) - return {'replication_status': 'failed-over'} + return {'replication_status': + fields.ReplicationStatus.FAILED_OVER} except Exception as e: LOG.exception('Unable to fail-over the volume %(id)s to the ' 'secondary back-end by switchrcrelationship ' @@ -276,7 +113,8 @@ class StorwizeSVCReplicationGlobalMirror( try: self.target_helpers.stop_relationship(target_vol, access=True) - return {'replication_status': 'failed-over'} + return {'replication_status': + fields.ReplicationStatus.FAILED_OVER} except Exception as e: msg = (_('Unable to fail-over the volume %(id)s to the ' 'secondary back-end, error: %(error)s') % @@ -291,7 +129,8 @@ class StorwizeSVCReplicationGlobalMirror( try: self.target_helpers.switch_relationship(rel_info['name'], aux=False) - return {'replication_status': 'enabled', + return {'replication_status': + fields.ReplicationStatus.ENABLED, 'status': 'available'} except Exception as e: msg = (_('Unable to fail-back the volume:%(vol)s to the ' @@ -318,6 +157,134 @@ class StorwizeSVCReplicationMetroMirror( driver, replication_target, target_helpers) +class StorwizeSVCReplicationGMCV(StorwizeSVCReplicationGlobalMirror): + """Support for Storwize/SVC global mirror with change volumes mode replication. + + Global Mirror with Change Volumes(GMCV) provides asynchronous replication + based on point-in-time copies of data. The volumes in a GMCV relationship + are referred to as the master (source) volume, master change volume, the + auxiliary (target) volume and auxiliary change volume. + """ + + asyncmirror = True + + def __init__(self, driver, replication_target=None, target_helpers=None): + super(StorwizeSVCReplicationGMCV, self).__init__( + driver, replication_target, target_helpers) + + def volume_replication_setup(self, context, vref): + LOG.debug('enter: volume_replication_setup: volume %s', vref['name']) + source_change_vol_name = (storwize_const.REPLICA_CHG_VOL_PREFIX + + vref['name']) + target_vol_name = storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name'] + target_change_vol_name = (storwize_const.REPLICA_CHG_VOL_PREFIX + + target_vol_name) + try: + src_attr = self.driver._helpers.get_vdisk_attributes( + vref['name']) + # Create source change volume if it doesn't exist + src_change_attr = self.driver._helpers.get_vdisk_attributes( + source_change_vol_name) + if not src_change_attr: + src_change_opts = self.driver._get_vdisk_params( + vref['volume_type_id']) + src_change_opts['iogrp'] = src_attr['IO_group_id'] + # Change volumes would usually be thin-provisioned + src_change_opts['autoexpand'] = True + self.driver._helpers.create_vdisk(source_change_vol_name, + six.text_type(vref['size']), + 'gb', + src_attr['mdisk_grp_id'], + src_change_opts) + # Create target volume if it doesn't exist + target_attr = self.target_helpers.get_vdisk_attributes( + target_vol_name) + if not target_attr: + target_opts = self.driver._get_vdisk_params( + vref['volume_type_id']) + target_pool = self.target.get('pool_name') + target_opts['iogrp'] = src_attr['IO_group_id'] + self.target_helpers.create_vdisk(target_vol_name, + six.text_type(vref['size']), + 'gb', + target_pool, + target_opts) + + # Create target change volume if it doesn't exist + target_change_attr = self.target_helpers.get_vdisk_attributes( + target_change_vol_name) + if not target_change_attr: + target_change_opts = self.driver._get_vdisk_params( + vref['volume_type_id']) + target_change_pool = self.target.get('pool_name') + target_change_opts['iogrp'] = src_attr['IO_group_id'] + # Change Volumes would usually be thin-provisioned + target_change_opts['autoexpand'] = True + self.target_helpers.create_vdisk(target_change_vol_name, + six.text_type(vref['size']), + 'gb', + target_change_pool, + target_change_opts) + + system_info = self.target_helpers.get_system_info() + # Get cycle_period_seconds + src_change_opts = self.driver._get_vdisk_params( + vref['volume_type_id']) + cycle_period_seconds = src_change_opts.get('cycle_period_seconds') + self.driver._helpers.create_relationship( + vref['name'], target_vol_name, system_info.get('system_name'), + self.asyncmirror, True, source_change_vol_name, + cycle_period_seconds) + # Set target change volume + self.target_helpers.change_relationship_changevolume( + target_vol_name, target_change_vol_name, False) + # Start gmcv relationship + self.driver._helpers.start_relationship(vref['name']) + except Exception as e: + msg = (_("Unable to set up gmcv mode replication for %(vol)s. " + "Exception: %(err)s.") % {'vol': vref['id'], + 'err': six.text_type(e)}) + LOG.exception(msg) + raise exception.VolumeDriverException(message=msg) + LOG.debug('leave: volume_replication_setup:volume %s', vref['name']) + + def failover_volume_host(self, context, vref): + LOG.debug('enter: failover_volume_host: vref=%(vref)s', + {'vref': vref['name']}) + # Make the aux volume writeable. + try: + self.target_helpers.stop_relationship( + storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name'], + access=True) + return {'replication_status': + fields.ReplicationStatus.FAILED_OVER} + except Exception as e: + msg = (_('Unable to fail-over the volume %(id)s to the ' + 'secondary back-end, error: %(error)s') % + {"id": vref['id'], "error": six.text_type(e)}) + LOG.exception(msg) + raise exception.VolumeDriverException(message=msg) + + def replication_failback(self, volume): + LOG.debug('enter: replication_failback: volume=%(volume)s', + {'volume': volume['name']}) + tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'] + rel_info = self.target_helpers.get_relationship_info(tgt_volume) + if rel_info: + try: + self.target_helpers.stop_relationship(tgt_volume, access=True) + self.target_helpers.start_relationship(tgt_volume, 'master') + return {'replication_status': + fields.ReplicationStatus.ENABLED, + 'status': 'available'} + except Exception as e: + msg = (_('Unable to fail-back the volume:%(vol)s to the ' + 'master back-end, error:%(error)s') % + {"vol": volume['name'], "error": six.text_type(e)}) + LOG.exception(msg) + raise exception.VolumeDriverException(message=msg) + + class StorwizeSVCReplicationManager(object): def __init__(self, driver, replication_target=None, target_helpers=None): @@ -330,6 +297,8 @@ class StorwizeSVCReplicationManager(object): self.driver, replication_target, self.target_helpers) self.metro_m = StorwizeSVCReplicationMetroMirror( self.driver, replication_target, self.target_helpers) + self.gmcv = StorwizeSVCReplicationGMCV( + self.driver, replication_target, self.target_helpers) def _run_ssh(self, cmd_list, check_exit_code=True, attempts=1): utils.check_ssh_injection(cmd_list) @@ -382,6 +351,8 @@ class StorwizeSVCReplicationManager(object): return self.global_m elif rep_type == storwize_const.METRO: return self.metro_m + elif rep_type == storwize_const.GMCV: + return self.gmcv else: return None diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py index 753ee782289..b97994ba246 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py @@ -32,16 +32,20 @@ REP_CAP_DEVS = (DEV_MODEL_SVC, DEV_MODEL_STORWIZE, DEV_MODEL_STORWIZE_V5000, # constants used for replication GLOBAL = 'global' METRO = 'metro' -VALID_REP_TYPES = (GLOBAL, METRO) +GMCV = 'gmcv' +GMCV_MULTI = 'multi' +VALID_REP_TYPES = (GLOBAL, METRO, GMCV) FAILBACK_VALUE = 'default' DEFAULT_RC_TIMEOUT = 3600 * 24 * 7 DEFAULT_RC_INTERVAL = 5 REPLICA_AUX_VOL_PREFIX = 'aux_' +REPLICA_CHG_VOL_PREFIX = 'chg_' # remote mirror copy status REP_CONSIS_SYNC = 'consistent_synchronized' +REP_CONSIS_COPYING = 'consistent_copying' REP_CONSIS_STOP = 'consistent_stopped' REP_SYNC = 'synchronized' REP_IDL = 'idling' 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 c46f69fa444..a5d16a29d99 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -124,6 +124,15 @@ storwize_svc_opts = [ default=None, help='Specifies the name of the pool in which mirrored copy ' 'is stored. Example: "pool2"'), + cfg.IntOpt('cycle_period_seconds', + default=300, + min=60, max=86400, + help='This defines an optional cycle period that applies to ' + 'Global Mirror relationships with a cycling mode of multi. ' + 'A Global Mirror relationship using the multi cycling_mode ' + 'performs a complete cycle at most once each period. ' + 'The default is 300 seconds, and the valid seconds ' + 'are 60-86400.'), ] CONF = cfg.CONF @@ -301,11 +310,14 @@ class StorwizeSSH(object): with excutils.save_and_reraise_exception(): LOG.error('Error mapping VDisk-to-host') - def mkrcrelationship(self, master, aux, system, asyncmirror): + def mkrcrelationship(self, master, aux, system, asyncmirror, + cyclingmode=False): ssh_cmd = ['svctask', 'mkrcrelationship', '-master', master, '-aux', aux, '-cluster', system] if asyncmirror: ssh_cmd.append('-global') + if cyclingmode: + ssh_cmd.extend(['-cyclingmode', 'multi']) return self.run_ssh_check_created(ssh_cmd) def rmrcrelationship(self, relationship, force=False): @@ -328,6 +340,30 @@ class StorwizeSSH(object): ssh_cmd.append(rc_rel) self.run_ssh_assert_no_output(ssh_cmd) + def ch_rcrelationship_cycleperiod(self, relationship, + cycle_period_seconds): + # Note: Can only change one attribute at a time, + # so define two ch_rcrelationship_xxx here + if cycle_period_seconds: + ssh_cmd = ['svctask', 'chrcrelationship'] + ssh_cmd.extend(['-cycleperiodseconds', + six.text_type(cycle_period_seconds)]) + ssh_cmd.append(relationship) + self.run_ssh_assert_no_output(ssh_cmd) + + def ch_rcrelationship_changevolume(self, relationship, + changevolume, master): + # Note: Can only change one attribute at a time, + # so define two ch_rcrelationship_xxx here + if changevolume: + ssh_cmd = ['svctask', 'chrcrelationship'] + if master: + ssh_cmd.extend(['-masterchange', changevolume]) + else: + ssh_cmd.extend(['-auxchange', changevolume]) + ssh_cmd.append(relationship) + self.run_ssh_assert_no_output(ssh_cmd) + def stoprcrelationship(self, relationship, access=False): ssh_cmd = ['svctask', 'stoprcrelationship'] if access: @@ -336,10 +372,8 @@ class StorwizeSSH(object): self.run_ssh_assert_no_output(ssh_cmd) def lsrcrelationship(self, rc_rel): - key_value = 'name=%s' % rc_rel - ssh_cmd = ['svcinfo', 'lsrcrelationship', '-filtervalue', - key_value, '-delim', '!'] - return self.run_ssh_info(ssh_cmd, with_header=True) + ssh_cmd = ['svcinfo', 'lsrcrelationship', '-delim', '!', rc_rel] + return self.run_ssh_info(ssh_cmd) def lspartnership(self, system_name): key_value = 'name=%s' % system_name @@ -1061,7 +1095,8 @@ class StorwizeHelpers(object): 'stretched_cluster': cluster_partner, 'replication': False, 'nofmtdisk': config.storwize_svc_vol_nofmtdisk, - 'mirror_pool': config.storwize_svc_mirror_pool} + 'mirror_pool': config.storwize_svc_mirror_pool, + 'cycle_period_seconds': config.cycle_period_seconds} return opt @staticmethod @@ -1084,6 +1119,12 @@ class StorwizeHelpers(object): reason=_('If compression is set to True, rsize must ' 'also be set (not equal to -1).')) + # Check cycle_period_seconds are in 60-86400 + if opts['cycle_period_seconds'] not in range(60, 86401): + raise exception.InvalidInput( + reason=_('cycle_period_seconds should be integer ' + 'between 60 and 86400.')) + iogs = StorwizeHelpers._get_valid_requested_io_groups(state, opts) if len(iogs) == 0: @@ -1152,6 +1193,7 @@ class StorwizeHelpers(object): # 'drivers' scope. if scope and scope != 'drivers': continue + if key in opts: this_type = type(opts[key]).__name__ if this_type == 'int': @@ -1599,7 +1641,8 @@ class StorwizeHelpers(object): wait_for_copy = False for map_id in mapping_ids: attrs = self._get_flashcopy_mapping_attributes(map_id) - if not attrs: + # We should ignore GMCV flash copies + if not attrs or 'yes' == attrs['rc_controlled']: continue source = attrs['source_vdisk_name'] target = attrs['target_vdisk_name'] @@ -1677,10 +1720,12 @@ class StorwizeHelpers(object): if vol_attrs['RC_name']: self.ssh.stoprcrelationship(vol_attrs['RC_name'], access=access) - def create_relationship(self, master, aux, system, asyncmirror): + def create_relationship(self, master, aux, system, asyncmirror, + cyclingmode=False, masterchange=None, + cycle_period_seconds=None): try: rc_id = self.ssh.mkrcrelationship(master, aux, system, - asyncmirror) + asyncmirror, cyclingmode) except exception.VolumeBackendAPIException as e: # CMMVC5959E is the code in Stowize storage, meaning that # there is a relationship that already has this name on the @@ -1690,7 +1735,31 @@ class StorwizeHelpers(object): # secondary back-end storage, the exception is raised. raise if rc_id: - self.start_relationship(master) + # We need setup master and aux change volumes for gmcv + # before we can start remote relationship + # aux change volume must be set on target site + if cycle_period_seconds: + self.change_relationship_cycleperiod(master, + cycle_period_seconds) + if masterchange: + self.change_relationship_changevolume(master, + masterchange, True) + else: + self.start_relationship(master) + + def change_relationship_changevolume(self, volume_name, + change_volume, master): + vol_attrs = self.get_vdisk_attributes(volume_name) + if vol_attrs['RC_name'] and change_volume: + self.ssh.ch_rcrelationship_changevolume(vol_attrs['RC_name'], + change_volume, master) + + def change_relationship_cycleperiod(self, volume_name, + cycle_period_seconds): + vol_attrs = self.get_vdisk_attributes(volume_name) + if vol_attrs['RC_name'] and cycle_period_seconds: + self.ssh.ch_rcrelationship_cycleperiod(vol_attrs['RC_name'], + cycle_period_seconds) def delete_relationship(self, volume_name): vol_attrs = self.get_vdisk_attributes(volume_name) @@ -1716,6 +1785,9 @@ class StorwizeHelpers(object): rel_info = self.get_relationship_info(vol_name) if rel_info: self.delete_relationship(vol_name) + # Delete change volume + self.delete_vdisk( + storwize_const.REPLICA_CHG_VOL_PREFIX + vol_name, False) self.delete_vdisk(vol_name, False) except Exception as e: msg = (_('Unable to delete the volume for ' @@ -2097,7 +2169,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._vdiskcopyops = {} self._vdiskcopyops_loop = None self.protocol = None - self.replication = None self._state = {'storage_nodes': {}, 'enabled_protocols': set(), 'compression_enabled': False, @@ -2140,9 +2211,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, # Update the storwize state self._update_storwize_state() - # Get the replication helpers - self.replication = storwize_rep.StorwizeSVCReplication.factory(self) - # Validate that the pool exists self._validate_pools_exist() @@ -2416,14 +2484,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, model_update = None - # The replication V2 has a higher priority than the replication V1. - # Check if V2 is available first, then check if V1 is available. if rep_type: replica_obj = self._get_replica_obj(rep_type) replica_obj.volume_replication_setup(ctxt, volume) - model_update = {'replication_status': 'enabled'} - elif opts.get('replication'): - model_update = self.replication.create_replica(ctxt, volume) + model_update = {'replication_status': + fields.ReplicationStatus.ENABLED} LOG.debug('leave: create_volume:\n volume: %(vol)s\n ' 'model_update %(model_update)s', @@ -2473,11 +2538,23 @@ class StorwizeSVCCommonDriver(san.SanDriver, def create_snapshot(self, snapshot): ctxt = context.get_admin_context() try: + # TODO(zhaochy): change to use snapshot.volume source_vol = self.db.volume_get(ctxt, snapshot['volume_id']) except Exception: msg = (_('create_snapshot: get source volume failed.')) LOG.error(msg) raise exception.VolumeDriverException(message=msg) + + rep_type = self._get_volume_replicated_type( + ctxt, None, source_vol['volume_type_id']) + if rep_type == storwize_const.GMCV: + # GMCV volume will have problem to failback + # when it has flash copy relationship besides change volumes + msg = _('create_snapshot: Create snapshot to ' + 'gmcv replication volume is not allowed.') + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + pool = utils.extract_host(source_vol['host'], 'pool') opts = self._get_vdisk_params(source_vol['volume_type_id']) self._helpers.create_copy(snapshot['volume_name'], snapshot['name'], @@ -2523,17 +2600,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, 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. if rep_type: self._validate_replication_enabled() replica_obj = self._get_replica_obj(rep_type) replica_obj.volume_replication_setup(ctxt, volume) - return {'replication_status': 'enabled'} - elif opts.get('replication'): - replica_status = self.replication.create_replica(ctxt, volume) - if replica_status: - return replica_status + return {'replication_status': fields.ReplicationStatus.ENABLED} def create_cloned_volume(self, tgt_volume, src_volume): """Creates a clone of the specified volume.""" @@ -2575,17 +2646,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, ctxt = context.get_admin_context() rep_type = self._get_volume_replicated_type(ctxt, tgt_volume) - # The replication V2 has a higher priority than the replication V1. - # Check if V2 is available first, then check if V1 is available. if rep_type: self._validate_replication_enabled() replica_obj = self._get_replica_obj(rep_type) replica_obj.volume_replication_setup(ctxt, tgt_volume) - return {'replication_status': 'enabled'} - elif opts.get('replication'): - replica_status = self.replication.create_replica(ctxt, tgt_volume) - if replica_status: - return replica_status + return {'replication_status': fields.ReplicationStatus.ENABLED} def extend_volume(self, volume, new_size): self._extend_volume_op(volume, new_size) @@ -2602,7 +2667,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, raise exception.VolumeDriverException(message=msg) if old_size is None: - old_size = volume['size'] + old_size = volume.size extend_amt = int(new_size) - old_size rel_info = self._helpers.get_relationship_info(volume_name) @@ -2610,28 +2675,52 @@ class StorwizeSVCCommonDriver(san.SanDriver, LOG.warning('_extend_volume_op: Extending a volume with ' 'remote copy is not recommended.') try: - tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + - volume['name']) rep_type = rel_info['copy_type'] + cyclingmode = rel_info['cycling_mode'] self._master_backend_helpers.delete_relationship( - volume['name']) - self._master_backend_helpers.extend_vdisk(volume['name'], + volume.name) + tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + + volume.name) + self._master_backend_helpers.extend_vdisk(volume.name, extend_amt) self._aux_backend_helpers.extend_vdisk(tgt_vol, extend_amt) tgt_sys = self._aux_backend_helpers.get_system_info() - self._master_backend_helpers.create_relationship( - volume['name'], tgt_vol, tgt_sys.get('system_name'), - True if storwize_const.GLOBAL == rep_type else False) + if storwize_const.GMCV_MULTI == cyclingmode: + tgt_change_vol = ( + storwize_const.REPLICA_CHG_VOL_PREFIX + + tgt_vol) + source_change_vol = ( + storwize_const.REPLICA_CHG_VOL_PREFIX + + volume.name) + self._master_backend_helpers.extend_vdisk( + source_change_vol, extend_amt) + self._aux_backend_helpers.extend_vdisk( + tgt_change_vol, extend_amt) + src_change_opts = self._get_vdisk_params( + volume.volume_type_id) + cycle_period_seconds = src_change_opts.get( + 'cycle_period_seconds') + self._master_backend_helpers.create_relationship( + volume.name, tgt_vol, tgt_sys.get('system_name'), + True, True, source_change_vol, cycle_period_seconds) + self._aux_backend_helpers.change_relationship_changevolume( + tgt_vol, tgt_change_vol, False) + self._master_backend_helpers.start_relationship( + volume.name) + else: + self._master_backend_helpers.create_relationship( + volume.name, tgt_vol, tgt_sys.get('system_name'), + True if storwize_const.GLOBAL == rep_type else False) except Exception as e: msg = (_('Failed to extend a volume with remote copy ' '%(volume)s. Exception: ' - '%(err)s.') % {'volume': volume['id'], + '%(err)s.') % {'volume': volume.id, 'err': e}) LOG.error(msg) raise exception.VolumeDriverException(message=msg) else: self._helpers.extend_vdisk(volume_name, extend_amt) - LOG.debug('leave: _extend_volume_op: volume %s', volume['id']) + LOG.debug('leave: _extend_volume_op: volume %s', volume.id) def add_vdisk_copy(self, volume, dest_pool, vol_type, auto_delete=False): return self._helpers.add_vdisk_copy(volume, dest_pool, @@ -2712,28 +2801,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, self.db.volume_admin_metadata_delete(ctxt.elevated(), volume['id'], 'vdiskcopyops') - def promote_replica(self, ctxt, volume): - return self.replication.promote_replica(volume) - - def reenable_replication(self, ctxt, volume): - return self.replication.reenable_replication(volume) - - def create_replica_test_volume(self, tgt_volume, src_volume): - if src_volume['size'] != tgt_volume['size']: - msg = (_('create_cloned_volume: Source and destination ' - 'size differ.')) - LOG.error(msg) - raise exception.InvalidInput(message=msg) - replica_status = self.replication.test_replica(tgt_volume, - src_volume) - return replica_status - - def get_replication_status(self, ctxt, volume): - replica_status = None - if self.replication: - replica_status = self.replication.get_replication_status(volume) - return replica_status - def _check_volume_copy_ops(self): LOG.debug("Enter: update volume copy status.") ctxt = context.get_admin_context() @@ -2795,6 +2862,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, def _replication_failback(self, ctxt, volumes): """Fail back all the volume on the secondary backend.""" + volumes_update = [] if not self._active_backend_id: LOG.info("Host has been failed back. doesn't need " @@ -2842,8 +2910,10 @@ class StorwizeSVCCommonDriver(san.SanDriver, if not rep_info: volumes_update.append( {'volume_id': volume['id'], - 'updates': {'replication_status': 'error', - 'status': 'error'}}) + 'updates': + {'replication_status': + fields.ReplicationStatus.ERROR, + 'status': 'error'}}) LOG.error('_failback_replica_volumes:no rc-releationship ' 'is established between master: %(master)s and ' 'aux %(aux)s. Please re-establish the ' @@ -2869,7 +2939,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, {'volume_id': volume.id}) volumes_update.append( {'volume_id': volume['id'], - 'updates': {'replication_status': 'error', + 'updates': {'replication_status': + fields.ReplicationStatus.ERROR, 'status': 'error'}}) LOG.debug('leave: _failback_replica_volumes ' 'volumes_update=%(volumes_update)s', @@ -2922,7 +2993,9 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'state': rep_info['state'], 'primary': rep_info['primary']}) try: - if rep_info['state'] != storwize_const.REP_CONSIS_SYNC: + if (rep_info['state'] not in + [storwize_const.REP_CONSIS_SYNC, + storwize_const.REP_CONSIS_COPYING]): if rep_info['primary'] == 'master': self._helpers.start_relationship(tgt_volume) else: @@ -2971,9 +3044,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'aux_vol': rep_info['aux_vdisk_name'], 'state': rep_info['state'], 'primary': rep_info['primary']}) - if rep_info['state'] == storwize_const.REP_CONSIS_SYNC: + if (rep_info['state'] in + [storwize_const.REP_CONSIS_SYNC, + storwize_const.REP_CONSIS_COPYING]): return True - if rep_info['state'] == storwize_const.REP_IDL_DISC: + elif rep_info['state'] == storwize_const.REP_IDL_DISC: msg = (_('Wait synchronize failed. volume: %(volume)s'), {'volume': volume}) LOG.error(msg) @@ -3035,7 +3110,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, volumes_update.append( {'volume_id': volume['id'], 'updates': - {'replication_status': 'error_failing-over', + {'replication_status': + fields.ReplicationStatus.FAILOVER_ERROR, 'status': 'error'}}) LOG.error('_failover_replica_volumes: no rc-' 'releationship is established for master:' @@ -3062,7 +3138,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, volumes_update.append( {'volume_id': volume['id'], 'updates': {'status': 'error', - 'replication_status': 'error_failing-over'}}) + 'replication_status': + fields.ReplicationStatus.FAILOVER_ERROR}}) LOG.debug('leave: _failover_replica_volumes ' 'volumes_update=%(volumes_update)s', {'volumes_update': volumes_update}) @@ -3129,9 +3206,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, def _get_volume_replicated_type(self, ctxt, volume, vol_type_id=None): replication_type = None - volume_type = (volume.volume_type if volume else - objects.VolumeType.get_by_name_or_id(ctxt, - vol_type_id)) + volume_type = None + volume_type_id = volume.volume_type_id if volume else vol_type_id + if volume_type_id: + volume_type = objects.VolumeType.get_by_name_or_id( + ctxt, volume_type_id) if volume_type: replication_type = self._get_specs_replicated_type(volume_type) return replication_type @@ -3271,7 +3350,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'is stored is not valid') % new_opts['mirror_pool']) raise exception.VolumeDriverException(message=msg) - # There are three options for rep_type: None, metro, global + # There are four options for rep_type: None, metro, global, gmcv 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']: @@ -3297,6 +3376,13 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'new_rep_type': new_rep_type}) LOG.error(msg) raise exception.VolumeDriverException(message=msg) + elif storwize_const.GMCV == new_rep_type: + # To gmcv, we may change cycle_period_seconds if needed + previous_cps = old_opts.get('cycle_period_seconds') + new_cps = new_opts.get('cycle_period_seconds') + if previous_cps != new_cps: + self._helpers.change_relationship_cycleperiod(volume.name, + new_cps) def retype(self, ctxt, volume, new_type, diff, host): """Convert the volume to be of the new type. @@ -3411,14 +3497,25 @@ class StorwizeSVCCommonDriver(san.SanDriver, if old_rep_type and not new_rep_type: self._aux_backend_helpers.delete_rc_volume(volume['name'], target_vol=True) - model_update = {'replication_status': 'disabled', + if storwize_const.GMCV == old_rep_type: + self._helpers.delete_vdisk( + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'], + False) + model_update = {'replication_status': + fields.ReplicationStatus.DISABLED, 'replication_driver_data': None, 'replication_extended_status': None} # Add replica if needed if not old_rep_type and new_rep_type: replica_obj = self._get_replica_obj(new_rep_type) replica_obj.volume_replication_setup(ctxt, volume) - model_update = {'replication_status': 'enabled'} + if storwize_const.GMCV == new_rep_type: + # Set cycle_period_seconds if needed + self._helpers.change_relationship_cycleperiod( + volume['name'], + new_opts.get('cycle_period_seconds')) + model_update = {'replication_status': + fields.ReplicationStatus.ENABLED} LOG.debug('exit: retype: ild=%(id)s, new_type=%(new_type)s,' 'diff=%(diff)s, host=%(host)s', {'id': volume['id'], @@ -3489,7 +3586,11 @@ class StorwizeSVCCommonDriver(san.SanDriver, 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'] + vol_rep_type = ( + storwize_const.GMCV if + storwize_const.GMCV_MULTI == rel_info['cycling_mode'] + else rel_info['copy_type']) + aux_info = self._aux_backend_helpers.get_system_info() if rel_info['aux_cluster_id'] != aux_info['system_id']: msg = (_("Failed to manage existing volume due to the aux " @@ -3505,6 +3606,22 @@ class StorwizeSVCCommonDriver(san.SanDriver, "the replication type of the volume to be managed is " "mismatch with the provided replication type.")) raise exception.ManageExistingVolumeTypeMismatch(reason=msg) + elif storwize_const.GMCV == rep_type: + if volume['volume_type_id']: + rep_opts = self._get_vdisk_params( + volume['volume_type_id'], + volume_metadata=volume.get('volume_metadata')) + # Check cycle_period_seconds + rep_cps = six.text_type(rep_opts.get('cycle_period_seconds')) + if rel_info['cycle_period_seconds'] != rep_cps: + msg = (_("Failed to manage existing volume due to " + "the cycle_period_seconds %(vol_cps)s of " + "the volume to be managed is mismatch with " + "cycle_period_seconds %(type_cps)s in " + "the provided gmcv replication type.") % + {'vol_cps': rel_info['cycle_period_seconds'], + 'type_cps': rep_cps}) + raise exception.ManageExistingVolumeTypeMismatch(reason=msg) if volume['volume_type_id']: opts = self._get_vdisk_params(volume['volume_type_id'], @@ -3585,7 +3702,15 @@ class StorwizeSVCCommonDriver(san.SanDriver, aux_vol = storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name'] self._aux_backend_helpers.rename_vdisk(rel_info['aux_vdisk_name'], aux_vol) - model_update = {'replication_status': 'enabled'} + if storwize_const.GMCV == vol_rep_type: + self._helpers.rename_vdisk( + rel_info['master_change_vdisk_name'], + storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name']) + self._aux_backend_helpers.rename_vdisk( + rel_info['aux_change_vdisk_name'], + storwize_const.REPLICA_CHG_VOL_PREFIX + aux_vol) + model_update = {'replication_status': + fields.ReplicationStatus.ENABLED} return model_update def manage_existing_get_size(self, volume, ref): @@ -3897,8 +4022,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'replication_targets': self._get_replication_targets(), 'replication_count': len(self._get_replication_targets()) }) - elif self.replication: - pool_stats.update(self.replication.get_replication_info()) except exception.VolumeBackendAPIException: msg = _('Failed getting details for pool %s.') % pool diff --git a/releasenotes/notes/storwize-gmcv-support-8aceee3f40eddb9f.yaml b/releasenotes/notes/storwize-gmcv-support-8aceee3f40eddb9f.yaml new file mode 100644 index 00000000000..fd0ad36b3ca --- /dev/null +++ b/releasenotes/notes/storwize-gmcv-support-8aceee3f40eddb9f.yaml @@ -0,0 +1,8 @@ +--- +features: + - Add global mirror with change volumes(gmcv) support and + user can manage gmcv replication volume by SVC driver. + An example to set a gmcv replication volume type, set + property replication_type as " gmcv", property + replication_enabled as " True" and set + property drivers:cycle_period_seconds as 500. \ No newline at end of file