From 7e59cad6569c30a362d64c51d87f0e8fa78104e2 Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 23 Nov 2016 06:27:13 -0800 Subject: [PATCH] backup of active 3par ISCSI bootable volume fails 1.Backup of ISCSI volume with chap enabled on it, fails because it choose online copy code path where copy volume is background process on 3par and we do not wait for process to complete and setting of any attribute on cloned volume failed during online copy of data. This patch ensures it choses non online copy code path for above scenario. 2.Backup of attached volume fails with snapCPG error Sometime, during online clone operation, we set snapCPG and userCPG on cloned volume but when we get cpg of cloned volume we get an error as these attributes do not set for cloned volume on 3par. This patch ensure if cpg is not available for volume from 3par it will take default value from 'host' attribute from volume param. Change-Id: I8dab8fd4e56c38557c46e4ae9a01fb6fead2a2a8 Closes-Bug: #1644238 Closes-Bug: #1646396 (cherry picked from commit 6543c0e13dcfff377395f981d5abd594f9c29d8e) --- .../unit/volume/drivers/hpe/test_hpe3par.py | 230 +++++++++++++++++- cinder/volume/drivers/hpe/hpe_3par_common.py | 43 +++- 2 files changed, 262 insertions(+), 11 deletions(-) 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)