diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index ef507270f30..096daa98e3c 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -2992,6 +2992,69 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): mock_client.assert_has_calls(expected) + @mock.patch.object(volume_types, 'get_volume_type') + def test_create_cloned_replicated_volume(self, _mock_volume_types): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + conf = self.setup_configuration() + self.replication_targets[0]['replication_mode'] = 'sync' + conf.replication_device = self.replication_targets + mock_client = self.setup_driver(config=conf) + + mock_client.getStorageSystemInfo.return_value = ( + {'id': self.CLIENT_ID}) + mock_client.getRemoteCopyGroup.side_effect = ( + hpeexceptions.HTTPNotFound) + mock_client.getCPG.return_value = {'domain': None} + + _mock_volume_types.return_value = { + 'name': 'replicated', + 'extra_specs': { + 'replication_enabled': ' True', + 'replication:mode': 'sync', + 'volume_type': self.volume_type_replicated}} + + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'name': mock.ANY} + task_id = 1 + mock_client.copyVolume.return_value = {'taskid': task_id} + mock_client.getTask.return_value = {'status': 1} + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + + type_id_replicated = HPE3PARBaseDriver.VOLUME_TYPE_ID_REPLICATED + volume = copy.deepcopy(self.volume_replicated) + src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'size': 2, 'status': 'available', + 'volume_type': 'replicated', + 'volume_type_id': type_id_replicated} + model_update = self.driver.create_cloned_volume(volume, src_vref) + self.assertEqual(model_update['replication_status'], + fields.ReplicationStatus.ENABLED) + + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(volume['id']) + src_vol_name = common._get_3par_vol_name(src_vref['id']) + optional = {'priority': 1} + comment = mock.ANY + + expected = [ + mock.call.createVolume(vol_name, 'OpenStackCPG', + 2048, comment), + mock.call.copyVolume( + src_vol_name, + vol_name, + None, + optional=optional), + mock.call.getTask(task_id), + mock.call.getRemoteCopyGroup('rcg-0DM4qZEVSKON-DXN-N'), + mock.call.startRemoteCopy('rcg-0DM4qZEVSKON-DXN-N') + ] + + mock_client.assert_has_calls(expected) + def test_migrate_volume(self): conf = { diff --git a/cinder/volume/drivers/hpe/hpe_3par_base.py b/cinder/volume/drivers/hpe/hpe_3par_base.py index 497fb7f3600..d4c730985f4 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_base.py +++ b/cinder/volume/drivers/hpe/hpe_3par_base.py @@ -147,7 +147,7 @@ class HPE3PARDriverBase(driver.ManageableVD, pass @volume_utils.trace - def create_volume(self, volume): + def create_volume(self, volume, perform_replica=True): return self.common.create_volume(volume) @volume_utils.trace diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py index 2718a3cf01a..22f4e0f2bbe 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_common.py +++ b/cinder/volume/drivers/hpe/hpe_3par_common.py @@ -306,11 +306,12 @@ class HPE3PARCommon(object): 4.0.19 - Update code to work with new WSAPI (of 2023). Bug #2015746 4.0.20 - Use small QoS Latency value. Bug #2018994 4.0.21 - Fix issue seen during retype/migrate. Bug #2026718 + 4.0.22 - Fixed clone of replicated volume. Bug #2021941 """ - VERSION = "4.0.21" + VERSION = "4.0.22" stats = {} @@ -2371,7 +2372,7 @@ class HPE3PARCommon(object): return volume_settings - def create_volume(self, volume): + def create_volume(self, volume, perform_replica=True): LOG.debug('CREATE VOLUME (%(disp_name)s: %(vol_name)s %(id)s on ' '%(host)s)', {'disp_name': volume['display_name'], @@ -2469,10 +2470,11 @@ class HPE3PARCommon(object): LOG.error("Exception: %s", ex) raise exception.CinderException(ex) - if (self._volume_of_replicated_type(volume, - hpe_tiramisu_check=True) - and self._do_volume_replication_setup(volume)): - replication_flag = True + if perform_replica: + if (self._volume_of_replicated_type(volume, + hpe_tiramisu_check=True) + and self._do_volume_replication_setup(volume)): + replication_flag = True except hpeexceptions.HTTPConflict: msg = _("Volume (%s) already exists on array") % volume_name @@ -2614,13 +2616,17 @@ class HPE3PARCommon(object): if str(src_vref['status']) == 'backing-up': back_up_process = True - # if the sizes of the 2 volumes are the same and except backup - # process for ISCSI volume with chap enabled on it. + # (i) if the sizes of the 2 volumes are the same and + # (ii) this is not a backup process for ISCSI volume with chap + # enabled on it and + # (iii) volume is not replicated # we can do an online copy, which is a background process # on the 3PAR that makes the volume instantly available. # We can't resize a volume, while it's being copied. if volume['size'] == src_vref['size'] and not ( - back_up_process and vol_chap_enabled): + back_up_process and vol_chap_enabled) and not ( + self._volume_of_replicated_type(volume, + hpe_tiramisu_check=True)): LOG.debug("Creating a clone of volume, using online copy.") type_info = self.get_volume_settings_from_type(volume) @@ -2656,18 +2662,11 @@ class HPE3PARCommon(object): LOG.error(msg) raise exception.CinderException(msg) - # v2 replication check - replication_flag = False - if (self._volume_of_replicated_type(volume, - hpe_tiramisu_check=True) - and self._do_volume_replication_setup(volume)): - replication_flag = True - if self._volume_of_hpe_tiramisu_type(volume): hpe_tiramisu = True return self._get_model_update(volume['host'], cpg, - replication=replication_flag, + replication=False, provider_location=self.client.id, hpe_tiramisu=hpe_tiramisu) else: @@ -2677,7 +2676,8 @@ class HPE3PARCommon(object): LOG.debug("Creating a clone of volume, using non-online copy.") # we first have to create the destination volume - model_update = self.create_volume(volume) + model_update = self.create_volume(volume, + perform_replica=False) optional = {'priority': 1} body = self.client.copyVolume(src_vol_name, vol_name, None, @@ -2694,6 +2694,24 @@ class HPE3PARCommon(object): LOG.debug('Copy volume completed: create_cloned_volume: ' 'id=%s.', volume['id']) + # v2 replication check + LOG.debug("v2 replication check") + replication_flag = False + if (self._volume_of_replicated_type(volume, + hpe_tiramisu_check=True) + and self._do_volume_replication_setup(volume)): + replication_flag = True + type_info = self.get_volume_settings_from_type(volume) + cpg = type_info['cpg'] + model_update = self._get_model_update( + volume['host'], cpg, + replication=True, + provider_location=self.client.id, + hpe_tiramisu=hpe_tiramisu) + + LOG.debug("replication_flag: %(flag)s", + {'flag': replication_flag}) + return model_update except hpeexceptions.HTTPForbidden: diff --git a/releasenotes/notes/hpe-3par-clone-of-repl-vol-914a6e0e105996b4.yaml b/releasenotes/notes/hpe-3par-clone-of-repl-vol-914a6e0e105996b4.yaml new file mode 100644 index 00000000000..56fb986ca09 --- /dev/null +++ b/releasenotes/notes/hpe-3par-clone-of-repl-vol-914a6e0e105996b4.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + HPE 3PAR driver `bug #2021941 + `_: + Fixed: Now clone of replicated volume can be created +