diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 6f41ffb2c..dd8cbdae3 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -1743,6 +1743,83 @@ class HPE3PARBaseDriver(object): expected + self.standard_logout) + def test_get_cpg_with_volume_return_usercpg(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'name': mock.ANY, + 'userCPG': HPE3PAR_CPG2} + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + volume = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'display_name': 'Foo Volume', + 'size': 2, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2)} + common = self.driver._login() + user_cpg = common.get_cpg(volume) + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(volume['id']) + self.assertEqual(HPE3PAR_CPG2, user_cpg) + expected = [mock.call.getVolume(vol_name)] + + mock_client.assert_has_calls( + self.standard_login + + expected) + + def test_get_cpg_with_volume_return_snapcpg(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'name': mock.ANY, + 'snapCPG': HPE3PAR_CPG2} + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + volume = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'display_name': 'Foo Volume', + 'size': 2, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2)} + common = self.driver._login() + snap_cpg = common.get_cpg(volume, allowSnap=True) + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(volume['id']) + self.assertEqual(HPE3PAR_CPG2, snap_cpg) + expected = [mock.call.getVolume(vol_name)] + + mock_client.assert_has_calls( + self.standard_login + + expected) + + def test_get_cpg_with_volume_return_no_cpg(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'name': mock.ANY} + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + volume = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'display_name': 'Foo Volume', + 'size': 2, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2)} + common = self.driver._login() + cpg_name = common.get_cpg(volume) + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(volume['id']) + self.assertEqual(HPE3PAR_CPG2, cpg_name) + expected = [mock.call.getVolume(vol_name)] + + mock_client.assert_has_calls( + self.standard_login + + expected) + def test_create_cloned_volume(self): # setup_mock_client drive with default configuration # and return the mock HTTP 3PAR client @@ -1762,7 +1839,7 @@ class HPE3PARBaseDriver(object): 'source_volid': HPE3PARBaseDriver.VOLUME_ID} src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, 'name': HPE3PARBaseDriver.VOLUME_NAME, - 'size': 2} + 'size': 2, 'status': 'available'} model_update = self.driver.create_cloned_volume(volume, src_vref) self.assertIsNone(model_update) @@ -1787,6 +1864,153 @@ class HPE3PARBaseDriver(object): expected + self.standard_logout) + def test_backup_iscsi_volume_with_chap_disabled(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'name': mock.ANY} + mock_client.copyVolume.return_value = {'taskid': 1} + mock_client.getVolumeMetaData.side_effect = hpeexceptions.HTTPNotFound + + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + + volume = {'name': HPE3PARBaseDriver.VOLUME_NAME, + 'id': HPE3PARBaseDriver.CLONE_ID, + 'display_name': 'Foo Volume', + 'size': 2, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2)} + src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'size': 2, 'status': 'backing-up'} + model_update = self.driver.create_cloned_volume(volume, src_vref) + self.assertIsNone(model_update) + + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(src_vref['id']) + # snapshot name is random + snap_name = mock.ANY + optional = mock.ANY + + expected = [ + mock.call.createSnapshot(snap_name, vol_name, optional), + mock.call.getVolume(snap_name), + mock.call.copyVolume( + snap_name, + 'osv-0DM4qZEVSKON-AAAAAAAAA', + HPE3PAR_CPG2, + {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True, + 'tdvv': False, 'online': True})] + + mock_client.assert_has_calls( + self.standard_login + + expected + + self.standard_logout) + + def test_create_clone_iscsi_volume_with_chap_disabled(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + config = self.setup_configuration() + config.hpe3par_iscsi_chap_enabled = True + mock_client = self.setup_driver(config=config) + mock_client.getVolume.return_value = {'name': mock.ANY} + mock_client.copyVolume.return_value = {'taskid': 1} + mock_client.getVolumeMetaData.side_effect = hpeexceptions.HTTPNotFound + + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + + volume = {'name': HPE3PARBaseDriver.VOLUME_NAME, + 'id': HPE3PARBaseDriver.CLONE_ID, + 'display_name': 'Foo Volume', + 'size': 2, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2)} + src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'size': 2, 'status': 'available'} + model_update = self.driver.create_cloned_volume(volume, src_vref) + self.assertIsNone(model_update) + + common = hpecommon.HPE3PARCommon(None) + vol_name = common._get_3par_vol_name(src_vref['id']) + # snapshot name is random + snap_name = mock.ANY + optional = mock.ANY + + expected = [ + mock.call.getVolumeMetaData(vol_name, + 'HPQ-cinder-CHAP-name'), + mock.call.createSnapshot(snap_name, vol_name, optional), + mock.call.getVolume(snap_name), + mock.call.copyVolume( + snap_name, + 'osv-0DM4qZEVSKON-AAAAAAAAA', + HPE3PAR_CPG2, + {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True, + 'tdvv': False, 'online': True})] + + mock_client.assert_has_calls( + self.standard_login + + expected + + self.standard_logout) + + def test_backup_iscsi_volume_with_chap_enabled(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + config = self.setup_configuration() + config.hpe3par_iscsi_chap_enabled = True + mock_client = self.setup_driver(config=config) + mock_client.getVolume.return_value = {'name': mock.ANY} + task_id = 1 + mock_client.copyVolume.return_value = {'taskid': task_id} + mock_client.getVolumeMetaData.return_value = { + 'value': 'random-key'} + 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 + + volume = {'name': HPE3PARBaseDriver.VOLUME_NAME, + 'id': HPE3PARBaseDriver.CLONE_ID, + 'display_name': 'Foo Volume', + 'size': 5, + 'host': volume_utils.append_host(self.FAKE_HOST, + HPE3PAR_CPG2), + 'source_volid': HPE3PARBaseDriver.VOLUME_ID} + src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, + 'name': HPE3PARBaseDriver.VOLUME_NAME, + 'size': 5, 'status': 'backing-up'} + model_update = self.driver.create_cloned_volume(volume, src_vref) + self.assertIsNone(model_update) + + 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.getVolumeMetaData(src_vol_name, + 'HPQ-cinder-CHAP-name'), + mock.call.createVolume(vol_name, 'fakepool', + 5120, comment), + mock.call.copyVolume( + src_vol_name, + vol_name, + None, + optional=optional), + mock.call.getTask(task_id), + ] + + mock_client.assert_has_calls( + self.standard_login + + expected + + self.standard_logout) + def test_create_cloned_volume_offline_copy(self): # setup_mock_client drive with default configuration # and return the mock HTTP 3PAR client @@ -1808,7 +2032,7 @@ class HPE3PARBaseDriver(object): 'source_volid': HPE3PARBaseDriver.VOLUME_ID} src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID, 'name': HPE3PARBaseDriver.VOLUME_NAME, - 'size': 2} + 'size': 2, 'status': 'available'} model_update = self.driver.create_cloned_volume(volume, src_vref) self.assertIsNone(model_update) @@ -1846,7 +2070,7 @@ class HPE3PARBaseDriver(object): src_vref = {'id': HPE3PARBaseDriver.CLONE_ID, 'name': HPE3PARBaseDriver.VOLUME_NAME, - 'size': 2} + 'size': 2, 'status': 'available'} volume = self.volume_qos.copy() host = "TEST_HOST" pool = "TEST_POOL" diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py index eafbb90da..edbb62f61 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_common.py +++ b/cinder/volume/drivers/hpe/hpe_3par_common.py @@ -244,10 +244,13 @@ class HPE3PARCommon(object): 3.0.24 - Fix terminate connection on failover 3.0.25 - Fix delete volume when online clone is active. bug #1349639 3.0.26 - Fix concurrent snapshot delete conflict. bug #1600104 + 3.0.27 - Fix snapCPG error during backup of attached volume. + Bug #1646396 and also ,Fix backup of attached ISCSI + and CHAP enabled volume.bug #1644238. """ - VERSION = "3.0.26" + VERSION = "3.0.27" stats = {} @@ -1626,11 +1629,18 @@ class HPE3PARCommon(object): def get_cpg(self, volume, allowSnap=False): volume_name = self._get_3par_vol_name(volume['id']) vol = self.client.getVolume(volume_name) + # Search for 'userCPG' in the get volume REST API, + # if found return userCPG , else search for snapCPG attribute + # when allowSnap=True. For the cases where 3PAR REST call for + # get volume doesn't have either userCPG or snapCPG , + # take the default value of cpg from 'host' attribute from volume param + LOG.debug("get volume response is: %s", vol) if 'userCPG' in vol: return vol['userCPG'] - elif allowSnap: + elif allowSnap and 'snapCPG' in vol: return vol['snapCPG'] - return None + else: + return volume_utils.extract_host(volume['host'], 'pool') def _get_3par_vol_comment(self, volume_name): vol = self.client.getVolume(volume_name) @@ -1983,13 +1993,31 @@ class HPE3PARCommon(object): try: vol_name = self._get_3par_vol_name(volume['id']) src_vol_name = self._get_3par_vol_name(src_vref['id']) + back_up_process = False + vol_chap_enabled = False - # if the sizes of the 2 volumes are the same + # Check whether a volume is ISCSI and CHAP enabled on it. + if self._client_conf['hpe3par_iscsi_chap_enabled']: + try: + vol_chap_enabled = self.client.getVolumeMetaData( + src_vol_name, 'HPQ-cinder-CHAP-name')['value'] + except hpeexceptions.HTTPNotFound: + LOG.debug("CHAP is not enabled on volume %(vol)s ", + {'vol': src_vref['id']}) + vol_chap_enabled = False + + # Check whether a process is a backup + 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. # 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']: - LOG.debug("Creating a clone of same size, using online copy.") + if volume['size'] == src_vref['size'] and not ( + back_up_process and vol_chap_enabled): + LOG.debug("Creating a clone of volume, using online copy.") # create a temporary snapshot snapshot = self._create_temp_snapshot(src_vref) @@ -2016,8 +2044,7 @@ class HPE3PARCommon(object): # The size of the new volume is different, so we have to # copy the volume and wait. Do the resize after the copy # is complete. - LOG.debug("Clone a volume with a different target size. " - "Using non-online copy.") + 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)