diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 6cb47a051a9..ffe4b7f077b 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -364,6 +364,7 @@ class HPE3PARBaseDriver(test.TestCase): 'TASK_DONE': TASK_DONE, 'TASK_ACTIVE': TASK_ACTIVE, 'HOST_EDIT_ADD': 1, + 'HOST_EDIT_REMOVE': 2, 'CHAP_INITIATOR': 1, 'CHAP_TARGET': 2, 'getPorts.return_value': { @@ -5966,6 +5967,11 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver): 'lun': None, 'type': 0}, {'active': True, 'volumeName': 'there-is-another-volume', + 'remoteName': '123456789012ABC', + 'lun': None, 'type': 0}, + {'active': True, + 'volumeName': 'there-is-another-volume', + 'remoteName': '123456789012ABC', 'lun': None, 'type': 0}, ] @@ -5983,7 +5989,18 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver): None, hostname=self.FAKE_HOST), mock.call.getHostVLUNs(self.FAKE_HOST), - mock.call.getHostVLUNs(self.FAKE_HOST)] + mock.call.modifyHost( + 'fakehost', + {'FCWWNs': ['123456789012345', '123456789054321'], + 'pathOperation': self.mock_client_conf['HOST_EDIT_REMOVE']}), + mock.call.getHostVLUNs(self.FAKE_HOST), mock.call.getPorts()] + + expect_conn = { + 'driver_volume_type': 'fibre_channel', + 'data': {'initiator_target_map': + {'123456789012345': ['0987654321234', '123456789000987'], + '123456789054321': ['0987654321234', '123456789000987']}, + 'target_wwn': ['0987654321234', '123456789000987']}} with mock.patch.object(hpecommon.HPE3PARCommon, '_create_client') as mock_create_client: @@ -5994,7 +6011,7 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver): self.standard_login + expect_less + self.standard_logout) - self.assertNotIn('initiator_target_map', conn_info['data']) + self.assertEqual(expect_conn, conn_info) def test_get_3par_host_from_wwn_iqn(self): mock_client = self.setup_driver() @@ -6996,7 +7013,10 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): None, hostname=self.FAKE_HOST), mock.call.getHostVLUNs(self.FAKE_HOST), - mock.call.deleteHost(self.FAKE_HOST), + mock.call.modifyHost( + 'fakehost', + {'pathOperation': 2, + 'iSCSINames': ['iqn.1993-08.org.debian:01:222']}), mock.call.removeVolumeMetaData( self.VOLUME_3PAR_NAME, CHAP_USER_KEY), mock.call.removeVolumeMetaData( @@ -7048,7 +7068,10 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): None, hostname=self.FAKE_HOST), mock.call.getHostVLUNs(self.FAKE_HOST), - mock.call.deleteHost(self.FAKE_HOST), + mock.call.modifyHost( + 'fakehost', + {'pathOperation': 2, + 'iSCSINames': ['iqn.1993-08.org.debian:01:222']}), mock.call.removeVolumeMetaData( self.VOLUME_3PAR_NAME, CHAP_USER_KEY)] @@ -7099,7 +7122,10 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): None, hostname=self.FAKE_HOST), mock.call.getHostVLUNs(self.FAKE_HOST), - mock.call.deleteHost(self.FAKE_HOST), + mock.call.modifyHost( + 'fakehost', + {'pathOperation': 2, + 'iSCSINames': ['iqn.1993-08.org.debian:01:222']}), mock.call.removeVolumeMetaData( self.VOLUME_3PAR_NAME, CHAP_USER_KEY), mock.call.removeVolumeMetaData( @@ -8590,7 +8616,10 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): None, hostname=self.FAKE_HOST), mock.call.getHostVLUNs(self.FAKE_HOST), - mock.call.deleteHost(self.FAKE_HOST), + mock.call.modifyHost( + 'fakehost', + {'pathOperation': 2, + 'iSCSINames': ['iqn.1993-08.org.debian:01:222']}), mock.call.removeVolumeMetaData( self.VOLUME_3PAR_NAME, CHAP_USER_KEY), mock.call.removeVolumeMetaData( diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py index 1ee22358b0e..8b2fe1cdecc 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_common.py +++ b/cinder/volume/drivers/hpe/hpe_3par_common.py @@ -266,11 +266,12 @@ class HPE3PARCommon(object): of QOS. bug #1717875 3.0.39 - Add support for revert to snapshot. 4.0.0 - Code refactor. + 4.0.1 - Added check to modify host after volume detach. bug #1730720 """ - VERSION = "4.0.0" + VERSION = "4.0.1" stats = {} @@ -1497,7 +1498,7 @@ class HPE3PARCommon(object): vlun_info['lun_id'], nsp) - def delete_vlun(self, volume, hostname): + def delete_vlun(self, volume, hostname, wwn=None, iqn=None): volume_name = self._get_3par_vol_name(volume['id']) vluns = self.client.getHostVLUNs(hostname) @@ -1505,6 +1506,7 @@ class HPE3PARCommon(object): # and any active VLUNs will be automatically removed. The template # VLUN are marked as active: False + modify_host = True volume_vluns = [] for vlun in vluns: @@ -1538,11 +1540,21 @@ class HPE3PARCommon(object): LOG.debug("All VLUNs removed from host %s", hostname) pass + if wwn is not None and not isinstance(wwn, list): + wwn = [wwn] + if iqn is not None and not isinstance(iqn, list): + iqn = [iqn] + for vlun in vluns: - if volume_name not in vlun['volumeName']: - # Found another volume - break - else: + if vlun.get('active'): + if (wwn is not None and vlun.get('remoteName').lower() in wwn)\ + or (iqn is not None and vlun.get('remoteName').lower() in + iqn): + # vlun with wwn/iqn exists so do not modify host. + modify_host = False + break + + if len(vluns) == 0: # We deleted the last vlun, so try to delete the host too. # This check avoids the old unnecessary try/fail when vluns exist # but adds a minor race condition if a vlun is manually deleted @@ -1566,6 +1578,21 @@ class HPE3PARCommon(object): "because: %(reason)s", {'name': volume_name, 'host': hostname, 'reason': ex.get_description()}) + elif modify_host: + if wwn is not None: + mod_request = {'pathOperation': self.client.HOST_EDIT_REMOVE, + 'FCWWNs': wwn} + else: + mod_request = {'pathOperation': self.client.HOST_EDIT_REMOVE, + 'iSCSINames': iqn} + try: + self.client.modifyHost(hostname, mod_request) + except Exception as ex: + LOG.info("3PAR vlun for volume '%(name)s' was deleted, " + "but the host '%(host)s' was not Modified " + "because: %(reason)s", + {'name': volume_name, 'host': hostname, + 'reason': ex.get_description()}) def _get_volume_type(self, type_id): ctxt = context.get_admin_context() @@ -2717,7 +2744,7 @@ class HPE3PARCommon(object): hostname = hosts['members'][0]['name'] try: - self.delete_vlun(volume, hostname) + self.delete_vlun(volume, hostname, wwn=wwn, iqn=iqn) return except hpeexceptions.HTTPNotFound as e: if 'host does not exist' in e.get_description(): @@ -2747,7 +2774,7 @@ class HPE3PARCommon(object): raise # try again with name retrieved from 3par - self.delete_vlun(volume, hostname) + self.delete_vlun(volume, hostname, wwn=wwn, iqn=iqn) def build_nsp(self, portPos): return '%s:%s:%s' % (portPos['node'], diff --git a/cinder/volume/drivers/hpe/hpe_3par_fc.py b/cinder/volume/drivers/hpe/hpe_3par_fc.py index d08f0c6e3ab..cca70401c1a 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_fc.py +++ b/cinder/volume/drivers/hpe/hpe_3par_fc.py @@ -107,10 +107,11 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): 3.0.11 - Handle manage and unmanage hosts present. bug #1648067 3.0.12 - Adds consistency group capability in generic volume groups. 4.0.0 - Adds base class. + 4.0.1 - Added check to remove FC zones. bug #1730720 """ - VERSION = "4.0.0" + VERSION = "4.0.1" # The name of the CI wiki page. CI_WIKI_NAME = "HPE_Storage_CI" @@ -256,18 +257,30 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): info = {'driver_volume_type': 'fibre_channel', 'data': {}} + zone_remove = True try: - common.client.getHostVLUNs(hostname) + 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") - target_wwns, init_targ_map, _numPaths = \ self._build_initiator_target_map(common, connector) info['data'] = {'target_wwn': target_wwns, 'initiator_target_map': init_targ_map} + return info finally: