self.VOLUME_3PAR_NAME,
None,
self.FAKE_HOST),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.deleteHost(self.FAKE_HOST),
mock.call.removeVolumeMetaData(
self.VOLUME_3PAR_NAME, CHAP_USER_KEY),
'name': self.FAKE_HOST
}]
}
+ mock_client.getVLUN.side_effect = [
+ hpexceptions.HTTPNotFound('fake')]
mock_client.getHostVLUNs.return_value = [
{'active': True,
'volumeName': self.VOLUME_3PAR_NAME,
'123456789054321']),
mock.call.getHost(self.FAKE_HOST),
mock.call.getPorts(),
+ mock.call.getVLUN(self.VOLUME_3PAR_NAME),
mock.call.createVLUN(
self.VOLUME_3PAR_NAME,
auto=True,
'name': self.FAKE_HOST
}]
}
+ mock_client.getVLUN.side_effect = [
+ hpexceptions.HTTPNotFound('fake')]
mock_client.getHostVLUNs.return_value = [
{'active': True,
'volumeName': self.VOLUME_3PAR_NAME,
mock.ANY,
mock.call.getHost(self.FAKE_HOST),
mock.call.getPorts(),
+ mock.call.getVLUN(self.VOLUME_3PAR_NAME),
mock.call.getPorts(),
mock.call.createVLUN(
self.VOLUME_3PAR_NAME,
'name': self.FAKE_HOST
}]
}
+ mock_client.getVLUN.side_effect = [
+ hpexceptions.HTTPNotFound('fake')]
mock_client.getHostVLUNs.return_value = [
{'active': True,
'volumeName': self.VOLUME_3PAR_NAME,
'123456789054321']),
mock.call.getHost(self.FAKE_HOST),
mock.call.getPorts(),
+ mock.call.getVLUN(self.VOLUME_3PAR_NAME),
mock.call.createVLUN(
self.VOLUME_3PAR_NAME,
auto=True,
effects = [
[{'active': True, 'volumeName': self.VOLUME_3PAR_NAME,
'lun': None, 'type': 0}],
+ hpexceptions.HTTPNotFound,
hpexceptions.HTTPNotFound]
mock_client.getHostVLUNs.side_effect = effects
self.VOLUME_3PAR_NAME,
None,
self.FAKE_HOST),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.deleteHost(self.FAKE_HOST),
mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.getPorts()]
effects = [
[{'active': True, 'volumeName': self.VOLUME_3PAR_NAME,
'lun': None, 'type': 0}],
+ hpexceptions.HTTPNotFound,
hpexceptions.HTTPNotFound]
mock_client.queryHost.return_value = {
self.VOLUME_3PAR_NAME,
None,
self.FAKE_HOST),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.deleteHost(self.FAKE_HOST),
mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.getPorts()]
self.VOLUME_3PAR_NAME,
None,
self.FAKE_HOST),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
mock.call.getHostVLUNs(self.FAKE_HOST)]
with mock.patch.object(hpcommon.HP3PARCommon,
'volumeName': self.VOLUME_3PAR_NAME,
'lun': self.TARGET_LUN, 'type': 0}]
mock_client.getVLUN.return_value = {
+ 'hostname': self.FAKE_HOST,
'lun': self.TARGET_LUN,
'portPos': {'node': 8, 'slot': 1, 'cardPort': 1}}
location = ("%(volume_name)s,%(lun_id)s,%(host)s,%(nsp)s" %
mock.call.getHost(self.FAKE_HOST),
mock.call.queryHost(iqns=['iqn.1993-08.org.debian:01:222']),
mock.call.getHost(self.FAKE_HOST),
- mock.call.getVLUN(self.VOLUME_3PAR_NAME),
- mock.call.createVLUN(
- self.VOLUME_3PAR_NAME,
- auto=True,
- hostname='fakehost',
- portPos={'node': 8, 'slot': 1, 'cardPort': 1}),
- mock.call.getHostVLUNs(self.FAKE_HOST)]
+ mock.call.getVLUN(self.VOLUME_3PAR_NAME)]
mock_client.assert_has_calls(
self.standard_login +
'volumeName': self.VOLUME_3PAR_NAME,
'lun': self.TARGET_LUN, 'type': 0}]
mock_client.getVLUN.return_value = {
+ 'hostname': self.FAKE_HOST,
'lun': self.TARGET_LUN,
'portPos': {'node': 8, 'slot': 1, 'cardPort': 1}}
location = ("%(volume_name)s,%(lun_id)s,%(host)s,%(nsp)s" %
mock.call.getHost(self.FAKE_HOST),
mock.call.queryHost(iqns=['iqn.1993-08.org.debian:01:222']),
mock.call.getHost(self.FAKE_HOST),
- mock.call.getVLUN(self.VOLUME_3PAR_NAME),
- mock.call.createVLUN(
- self.VOLUME_3PAR_NAME,
- auto=True,
- hostname='fakehost',
- portPos={'node': 8, 'slot': 1, 'cardPort': 1}),
- mock.call.getHostVLUNs(self.FAKE_HOST)]
+ mock.call.getVLUN(self.VOLUME_3PAR_NAME)]
mock_client.assert_has_calls(
self.standard_login +
2.0.43 - Report the capability of supporting multiattach
2.0.44 - Update help strings to reduce the 3PAR user role requirements
2.0.45 - Python 3 fixes
+ 2.0.46 - Improved VLUN creation and deletion logic. #1469816
"""
- VERSION = "2.0.45"
+ VERSION = "2.0.46"
stats = {}
volume_name = self._get_3par_vol_name(volume['id'])
vluns = self.client.getHostVLUNs(hostname)
+ # Find all the VLUNs associated with the volume. The VLUNs will then
+ # be split into groups based on the active status of the VLUN. If there
+ # are active VLUNs detected a delete will be attempted on them. If
+ # there are no active VLUNs but there are inactive VLUNs, then the
+ # inactive VLUNs will be deleted. The inactive VLUNs are the templates
+ # on the 3PAR backend.
+ active_volume_vluns = []
+ inactive_volume_vluns = []
+ volume_vluns = []
+
for vlun in vluns:
if volume_name in vlun['volumeName']:
- break
- else:
- LOG.info(_LI("3PAR vlun for volume %(name)s not found on host "
- "%(host)s"), {'name': volume_name, 'host': hostname})
+ if vlun['active']:
+ active_volume_vluns.append(vlun)
+ else:
+ inactive_volume_vluns.append(vlun)
+ if active_volume_vluns:
+ volume_vluns = active_volume_vluns
+ elif inactive_volume_vluns:
+ volume_vluns = inactive_volume_vluns
+
+ if not volume_vluns:
+ msg = (
+ _LW("3PAR vlun for volume %(name)s not found on "
+ "host %(host)s"), {'name': volume_name, 'host': hostname})
+ LOG.warning(msg)
return
# VLUN Type of MATCHED_SET 4 requires the port to be provided
- if self.VLUN_TYPE_MATCHED_SET == vlun['type']:
- self.client.deleteVLUN(volume_name, vlun['lun'], hostname,
- vlun['portPos'])
- else:
- self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
+ removed_luns = []
+ for vlun in volume_vluns:
+ if self.VLUN_TYPE_MATCHED_SET == vlun['type']:
+ self.client.deleteVLUN(volume_name, vlun['lun'], hostname,
+ vlun['portPos'])
+ else:
+ # This is HOST_SEES or a type that is not MATCHED_SET.
+ # By deleting one VLUN, all the others should be deleted, too.
+ if vlun['lun'] not in removed_luns:
+ self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
+ removed_luns.append(vlun['lun'])
# Determine if there are other volumes attached to the host.
# This will determine whether we should try removing host from host set
# and deleting the host.
+ vluns = []
+ try:
+ vluns = self.client.getHostVLUNs(hostname)
+ except hpexceptions.HTTPNotFound:
+ LOG.debug("All VLUNs removed from host %s", hostname)
+ pass
+
for vlun in vluns:
if volume_name not in vlun['volumeName']:
# Found another volume
2.0.14 - Removed usage of host name cache #1398914
2.0.15 - Added support for updated detach_volume attachment.
2.0.16 - Added encrypted property to initialize_connection #1439917
+ 2.0.17 - Improved VLUN creation and deletion logic. #1469816
"""
- VERSION = "2.0.16"
+ VERSION = "2.0.17"
def __init__(self, *args, **kwargs):
super(HP3PARFCDriver, self).__init__(*args, **kwargs)
target_wwns, init_targ_map, numPaths = \
self._build_initiator_target_map(common, connector)
- # now that we have a host, create the VLUN
- if self.lookup_service is not None and numPaths == 1:
- nsp = None
- active_fc_port_list = common.get_active_fc_target_ports()
- for port in active_fc_port_list:
- if port['portWWN'].lower() == target_wwns[0].lower():
- nsp = port['nsp']
- break
- vlun = common.create_vlun(volume, host, nsp)
+ # check if a VLUN already exists for this host
+ existing_vlun = None
+ try:
+ vol_name = common._get_3par_vol_name(volume['id'])
+ existing_vlun = common.client.getVLUN(vol_name)
+ except hpexceptions.HTTPNotFound:
+ # ignore, vlun will be created later
+ pass
+
+ vlun = None
+ if not existing_vlun or host['name'] != existing_vlun['hostname']:
+ # now that we have a host, create the VLUN
+ if self.lookup_service is not None and numPaths == 1:
+ nsp = None
+ active_fc_port_list = common.get_active_fc_target_ports()
+ for port in active_fc_port_list:
+ if port['portWWN'].lower() == target_wwns[0].lower():
+ nsp = port['nsp']
+ break
+ vlun = common.create_vlun(volume, host, nsp)
+ else:
+ vlun = common.create_vlun(volume, host)
else:
- vlun = common.create_vlun(volume, host)
+ vlun = existing_vlun
info = {'driver_volume_type': 'fibre_channel',
'data': {'target_lun': vlun['lun'],
2.0.15 - Added support for updated detach_volume attachment.
2.0.16 - Added encrypted property to initialize_connection #1439917
2.0.17 - Python 3 fixes
+ 2.0.18 - Improved VLUN creation and deletion logic. #1469816
"""
- VERSION = "2.0.17"
+ VERSION = "2.0.18"
def __init__(self, *args, **kwargs):
super(HP3PARISCSIDriver, self).__init__(*args, **kwargs)
connector)
least_used_nsp = None
+ existing_vlun = None
try:
vol_name = common._get_3par_vol_name(volume['id'])
existing_vlun = common.client.getVLUN(vol_name)
common,
host['name'])
- # now that we have a host, create the VLUN
- vlun = common.create_vlun(volume, host, least_used_nsp)
+ vlun = None
+ if not existing_vlun or host['name'] != existing_vlun['hostname']:
+ # now that we have a host, create the VLUN
+ vlun = common.create_vlun(volume, host, least_used_nsp)
+ else:
+ vlun = existing_vlun
if least_used_nsp is None:
LOG.warning(_LW("Least busy iSCSI port not found, "