From abca1abc7b01fc1d85af8b9cfa5b646abafc9d4a Mon Sep 17 00:00:00 2001 From: Vivek Soni Date: Thu, 1 Mar 2018 01:37:58 -0800 Subject: [PATCH] 3PAR: Add `force detach` support Add support to force detach a volume from all hosts on 3PAR. Change-Id: I2ddd0be0d59018db43dca297585d5cb2ee459ede Closes-bug: #1686745 --- .../unit/volume/drivers/hpe/test_hpe3par.py | 39 ++++++++++++++++ cinder/volume/drivers/hpe/hpe_3par_common.py | 35 ++++++++++---- cinder/volume/drivers/hpe/hpe_3par_fc.py | 46 +++++++++++-------- cinder/volume/drivers/hpe/hpe_3par_iscsi.py | 17 ++++--- .../notes/bug-1686745-e8f1569455f998ba.yaml | 4 ++ 5 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 releasenotes/notes/bug-1686745-e8f1569455f998ba.yaml diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 23d889f2da0..116eea9434d 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -7015,6 +7015,45 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver): expected + self.standard_logout) + def test_force_detach_volume(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + + mock_client.getVLUNs.return_value = { + 'members': [{ + 'active': False, + 'volumeName': self.VOLUME_3PAR_NAME, + 'hostname': self.FAKE_HOST, + 'lun': None, 'type': 0}]} + + mock_client.queryHost.return_value = { + 'members': [{ + 'name': self.FAKE_HOST + }] + } + + mock_client.getHostVLUNs.side_effect = hpeexceptions.HTTPNotFound + + expected = [ + mock.call.getVLUNs(), + mock.call.deleteVLUN( + self.VOLUME_3PAR_NAME, + None, + hostname=self.FAKE_HOST), + mock.call.getHostVLUNs(self.FAKE_HOST), + mock.call.deleteHost(self.FAKE_HOST)] + + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + self.driver.terminate_connection(self.volume, None) + + mock_client.assert_has_calls( + self.standard_login + + expected + + self.standard_logout) + @mock.patch('cinder.zonemanager.utils.create_lookup_service') def test_terminate_connection_with_lookup(self, mock_lookup): # setup_mock_client drive with default configuration diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py index 4a08f8f59b7..6a86815e827 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_common.py +++ b/cinder/volume/drivers/hpe/hpe_3par_common.py @@ -265,11 +265,12 @@ class HPE3PARCommon(object): differs from volume present in the source group in terms of extra-specs. bug #1744025 4.0.6 - Monitor task of promoting a virtual copy. bug #1749642 + 4.0.7 - Handle force detach case. bug #1686745 """ - VERSION = "4.0.6" + VERSION = "4.0.7" stats = {} @@ -1659,7 +1660,11 @@ class HPE3PARCommon(object): def delete_vlun(self, volume, hostname, wwn=None, iqn=None): volume_name = self._get_3par_vol_name(volume['id']) - vluns = self.client.getHostVLUNs(hostname) + if hostname: + vluns = self.client.getHostVLUNs(hostname) + else: + # In case of 'force detach', hostname is None + vluns = self.client.getVLUNs()['members'] # When deleteing VLUNs, you simply need to remove the template VLUN # and any active VLUNs will be automatically removed. The template @@ -1681,6 +1686,8 @@ class HPE3PARCommon(object): # VLUN Type of MATCHED_SET 4 requires the port to be provided for vlun in volume_vluns: + if hostname is None: + hostname = vlun.get('hostname') if 'portPos' in vlun: self.client.deleteVLUN(volume_name, vlun['lun'], hostname=hostname, @@ -1721,6 +1728,8 @@ class HPE3PARCommon(object): # host, so it is worth the unlikely risk. try: + # TODO(sonivi): since multiattach is not supported for now, + # delete only single host, if its not exported to volume. self._delete_3par_host(hostname) except Exception as ex: # Any exception down here is only logged. The vlun is deleted. @@ -2928,8 +2937,9 @@ class HPE3PARCommon(object): elif iqn: hosts = self.client.queryHost(iqns=[iqn]) - if hosts and hosts['members'] and 'name' in hosts['members'][0]: - hostname = hosts['members'][0]['name'] + if hosts is not None: + if hosts and hosts['members'] and 'name' in hosts['members'][0]: + hostname = hosts['members'][0]['name'] try: self.delete_vlun(volume, hostname, wwn=wwn, iqn=iqn) @@ -2950,12 +2960,19 @@ class HPE3PARCommon(object): "secondary target.") return else: - # use the wwn to see if we can find the hostname - hostname = self._get_3par_hostname_from_wwn_iqn(wwn, iqn) - # no 3par host, re-throw - if hostname is None: - LOG.error("Exception: %s", e) + if hosts is None: + # In case of 'force detach', hosts is None + LOG.exception("Exception: %s", e) raise + else: + # use the wwn to see if we can find the hostname + hostname = self._get_3par_hostname_from_wwn_iqn( + wwn, + iqn) + # no 3par host, re-throw + if hostname is None: + LOG.exception("Exception: %s", e) + raise else: # not a 'host does not exist' HTTPNotFound exception, re-throw LOG.error("Exception: %s", e) diff --git a/cinder/volume/drivers/hpe/hpe_3par_fc.py b/cinder/volume/drivers/hpe/hpe_3par_fc.py index d30ecdbce66..a032e0a5d6e 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_fc.py +++ b/cinder/volume/drivers/hpe/hpe_3par_fc.py @@ -108,10 +108,11 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): 4.0.1 - Added check to remove FC zones. bug #1730720 4.0.2 - Create one vlun in single path configuration. bug #1727176 4.0.3 - Create FC vlun as host sees. bug #1734505 + 4.0.4 - Handle force detach case. bug #1686745 """ - VERSION = "4.0.3" + VERSION = "4.0.4" # The name of the CI wiki page. CI_WIKI_NAME = "HPE_Storage_CI" @@ -209,28 +210,35 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): """Driver entry point to unattach a volume from an instance.""" common = self._login() try: - hostname = common._safe_hostname(connector['host']) - common.terminate_connection(volume, hostname, - wwn=connector['wwpns']) + is_force_detach = connector is None + if is_force_detach: + common.terminate_connection(volume, None, None) + # TODO(sonivi): remove zones, if not required + # for now, do not remove zones + zone_remove = False + else: + hostname = common._safe_hostname(connector['host']) + common.terminate_connection(volume, hostname, + wwn=connector['wwpns']) + + zone_remove = True + try: + vluns = common.client.getHostVLUNs(hostname) + except hpeexceptions.HTTPNotFound: + # No more exports for this host. + pass + else: + # Vlun exists, so check for wwpn entry. + for wwpn in connector.get('wwpns'): + for vlun in vluns: + if (vlun.get('active') and + vlun.get('remoteName') == wwpn.upper()): + zone_remove = False + break info = {'driver_volume_type': 'fibre_channel', 'data': {}} - zone_remove = True - try: - vluns = common.client.getHostVLUNs(hostname) - except hpeexceptions.HTTPNotFound: - # No more exports for this host. - pass - else: - # Vlun exists, so check for wwpn entry. - for wwpn in connector.get('wwpns'): - for vlun in vluns: - if vlun.get('active') and \ - vlun.get('remoteName') == wwpn.upper(): - zone_remove = False - break - if zone_remove: LOG.info("Need to remove FC Zone, building initiator " "target map") diff --git a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py index cd550e2a276..d18087e5d7e 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py @@ -122,10 +122,11 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): 4.0.0 - Adds base class. 4.0.1 - Update CHAP on host record when volume is migrated to new compute host. bug # 1737181 + 4.0.2 - Handle force detach case. bug #1686745 """ - VERSION = "4.0.1" + VERSION = "4.0.2" # The name of the CI wiki page. CI_WIKI_NAME = "HPE_Storage_CI" @@ -364,11 +365,15 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): """Driver entry point to unattach a volume from an instance.""" common = self._login() try: - hostname = common._safe_hostname(connector['host']) - common.terminate_connection( - volume, - hostname, - iqn=connector['initiator']) + is_force_detach = connector is None + if is_force_detach: + common.terminate_connection(volume, None, None) + else: + hostname = common._safe_hostname(connector['host']) + common.terminate_connection( + volume, + hostname, + iqn=connector['initiator']) self._clear_chap_3par(common, volume) finally: self._logout(common) diff --git a/releasenotes/notes/bug-1686745-e8f1569455f998ba.yaml b/releasenotes/notes/bug-1686745-e8f1569455f998ba.yaml new file mode 100644 index 00000000000..10c400ce1a1 --- /dev/null +++ b/releasenotes/notes/bug-1686745-e8f1569455f998ba.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support to force detach a volume from all hosts on 3PAR.