Fix Cisco Initiator zoning updates
Instead of current methodology of deleting and recreating zones in order to update WWPN members, changed logic to perform a zone_update to modify existing zonemembers for a given zone. Fix prevents targets from losing connectivity to initiators during an update. Change-Id: I023ffaa2d4185c686a886675f931d5da522a9591 Closes-Bug: #1611043
This commit is contained in:
parent
a4ac6a98d1
commit
27b7a2907b
@ -241,12 +241,40 @@ class TestCiscoFCZoneClientCLI(cli.CiscoFCZoneClientCLI, test.TestCase):
|
||||
|
||||
@mock.patch.object(cli.CiscoFCZoneClientCLI, '_ssh_execute')
|
||||
@mock.patch.object(cli.CiscoFCZoneClientCLI, '_cfg_save')
|
||||
def test__add_zones_with_update(self, ssh_execute_mock, cfg_save_mock):
|
||||
self.add_zones(new_zone, False, self.fabric_vsan,
|
||||
active_zoneset_multiple_zones,
|
||||
zoning_status_basic)
|
||||
self.assertEqual(2, ssh_execute_mock.call_count)
|
||||
self.assertEqual(2, cfg_save_mock.call_count)
|
||||
def test__update_zones_add(self, cfg_save_mock, ssh_execute_mock):
|
||||
self.update_zones(new_zone, False, self.fabric_vsan,
|
||||
ZoneConstant.ZONE_ADD, active_zoneset_multiple_zones,
|
||||
zoning_status_basic)
|
||||
ssh_cmd = [['conf'],
|
||||
['zoneset', 'name', 'OpenStack_Cfg', 'vsan',
|
||||
self.fabric_vsan],
|
||||
['zone', 'name',
|
||||
'openstack10000012345678902001009876543210'],
|
||||
['member', 'pwwn', '10:00:00:12:34:56:78:90'],
|
||||
['member', 'pwwn', '20:01:00:98:76:54:32:10'],
|
||||
['end']]
|
||||
|
||||
self.assertEqual(1, cfg_save_mock.call_count)
|
||||
ssh_execute_mock.assert_called_once_with(ssh_cmd, True, 1)
|
||||
|
||||
@mock.patch.object(cli.CiscoFCZoneClientCLI, '_ssh_execute')
|
||||
@mock.patch.object(cli.CiscoFCZoneClientCLI, '_cfg_save')
|
||||
def test__update_zones_remove(self, cfg_save_mock, ssh_execute_mock):
|
||||
self.update_zones(new_zone, False, self.fabric_vsan,
|
||||
ZoneConstant.ZONE_REMOVE,
|
||||
active_zoneset_multiple_zones,
|
||||
zoning_status_basic)
|
||||
ssh_cmd = [['conf'],
|
||||
['zoneset', 'name', 'OpenStack_Cfg', 'vsan',
|
||||
self.fabric_vsan],
|
||||
['zone', 'name',
|
||||
'openstack10000012345678902001009876543210'],
|
||||
['no', 'member', 'pwwn', '10:00:00:12:34:56:78:90'],
|
||||
['no', 'member', 'pwwn', '20:01:00:98:76:54:32:10'],
|
||||
['end']]
|
||||
|
||||
self.assertEqual(1, cfg_save_mock.call_count)
|
||||
ssh_execute_mock.assert_called_once_with(ssh_cmd, True, 1)
|
||||
|
||||
def test__parse_ns_output(self):
|
||||
return_wwn_list = []
|
||||
|
@ -174,24 +174,6 @@ class CiscoFCZoneClientCLI(object):
|
||||
['zoneset', 'name', cfg_name, 'vsan', fabric_vsan]]
|
||||
|
||||
for zone in zones.keys():
|
||||
# if zone exists, its an update. Delete & insert
|
||||
LOG.debug("Update call")
|
||||
if zone in zone_list:
|
||||
# Response from get_active_zone_set strips colons from WWPNs
|
||||
current_zone = set(zone_list[zone])
|
||||
new_wwpns = map(lambda x: x.lower().replace(':', ''),
|
||||
zones[zone])
|
||||
new_zone = set(new_wwpns)
|
||||
|
||||
if current_zone != new_zone:
|
||||
try:
|
||||
self.delete_zones(zone, activate, fabric_vsan,
|
||||
active_zone_set, zone_status)
|
||||
except exception.CiscoZoningCliException:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Deleting zone failed %s"), zone)
|
||||
LOG.debug("Deleted Zone before insert : %s", zone)
|
||||
|
||||
zone_cmds.append(['zone', 'name', zone])
|
||||
|
||||
for member in zones[zone]:
|
||||
@ -214,6 +196,72 @@ class CiscoFCZoneClientCLI(object):
|
||||
LOG.error(msg)
|
||||
raise exception.CiscoZoningCliException(reason=msg)
|
||||
|
||||
def update_zones(self, zones, activate, fabric_vsan, operation,
|
||||
active_zone_set, zone_status):
|
||||
"""Update the zone configuration.
|
||||
|
||||
This method will update the zone configuration passed by user.
|
||||
|
||||
:param zones: zone names mapped to members. Zone members
|
||||
are colon separated but case-insensitive
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{ zonename1:[zonememeber1, zonemember2,...],
|
||||
zonename2:[zonemember1, zonemember2,...]...}
|
||||
|
||||
e.g:
|
||||
|
||||
{
|
||||
'openstack50060b0000c26604201900051ee8e329':
|
||||
['50:06:0b:00:00:c2:66:04',
|
||||
'20:19:00:05:1e:e8:e3:29']
|
||||
}
|
||||
|
||||
:param activate: True will activate the zone config.
|
||||
:param operation: zone add or zone remove
|
||||
:param fabric_vsan: Virtual San #
|
||||
:param active_zone_set: Active zone set dict retrieved from
|
||||
get_active_zone_set method
|
||||
:param zone_status: Status of the zone
|
||||
:raises: CiscoZoningCliException
|
||||
"""
|
||||
|
||||
LOG.debug("Update Zones - Operation: %(op)s - Zones "
|
||||
"passed: %(zones)s",
|
||||
{'op': operation, 'zones': zones})
|
||||
|
||||
cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
|
||||
|
||||
zone_cmds = [['conf'],
|
||||
['zoneset', 'name', cfg_name, 'vsan', fabric_vsan]]
|
||||
zone_mod_cmd = []
|
||||
if operation == ZoneConstant.ZONE_ADD:
|
||||
zone_mod_cmd = ['member', 'pwwn']
|
||||
elif operation == ZoneConstant.ZONE_REMOVE:
|
||||
zone_mod_cmd = ['no', 'member', 'pwwn']
|
||||
|
||||
for zone, zone_members in zones.items():
|
||||
zone_cmds.append(['zone', 'name', zone])
|
||||
for member in zone_members:
|
||||
zone_cmds.append(zone_mod_cmd + [member])
|
||||
zone_cmds.append(['end'])
|
||||
|
||||
try:
|
||||
LOG.debug("Update zones: Config cmd to run: %s", zone_cmds)
|
||||
self._ssh_execute(zone_cmds, True, 1)
|
||||
|
||||
if activate:
|
||||
self.activate_zoneset(cfg_name, fabric_vsan, zone_status)
|
||||
self._cfg_save()
|
||||
except Exception as e:
|
||||
|
||||
msg = (_("Updating and activating zone set failed: "
|
||||
"(Zone set=%(zoneset)s error=%(err)s).")
|
||||
% {'zoneset': cfg_name, 'err': six.text_type(e)})
|
||||
LOG.error(msg)
|
||||
raise exception.CiscoZoningCliException(reason=msg)
|
||||
|
||||
def activate_zoneset(self, cfgname, fabric_vsan, zone_status):
|
||||
"""Method to Activate the zone config. Param cfgname - ZonesetName."""
|
||||
|
||||
|
@ -39,6 +39,7 @@ from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI
|
||||
from cinder import interface
|
||||
from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as fabric_opts
|
||||
from cinder.zonemanager.drivers.cisco import fc_zone_constants as ZoneConstant
|
||||
from cinder.zonemanager.drivers import driver_utils
|
||||
from cinder.zonemanager.drivers import fc_zone_driver
|
||||
from cinder.zonemanager import utils as zm_utils
|
||||
@ -167,6 +168,7 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
# push changes to fabric.
|
||||
for initiator_key in initiator_target_map.keys():
|
||||
zone_map = {}
|
||||
zone_update_map = {}
|
||||
initiator = initiator_key.lower()
|
||||
t_list = initiator_target_map[initiator_key]
|
||||
if zoning_policy == 'initiator-target':
|
||||
@ -209,19 +211,28 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
self.configuration.cisco_zone_name_prefix,
|
||||
SUPPORTED_CHARS))
|
||||
|
||||
if len(zone_names) > 0 and (zone_name in zone_names):
|
||||
zone_members = zone_members + filter(
|
||||
lambda x: x not in zone_members,
|
||||
cfgmap_from_fabric['zones'][zone_name])
|
||||
zone_map[zone_name] = zone_members
|
||||
# If zone exists, then perform a update_zone and add
|
||||
# new members into existing zone.
|
||||
if zone_name and (zone_name in zone_names):
|
||||
zone_members = filter(
|
||||
lambda x: x not in
|
||||
cfgmap_from_fabric['zones'][zone_name],
|
||||
zone_members)
|
||||
if zone_members:
|
||||
zone_update_map[zone_name] = zone_members
|
||||
else:
|
||||
zone_map[zone_name] = zone_members
|
||||
else:
|
||||
msg = _("Zoning Policy: %s, not"
|
||||
" recognized") % zoning_policy
|
||||
LOG.error(msg)
|
||||
raise exception.FCZoneDriverException(msg)
|
||||
|
||||
if len(zone_map) > 0:
|
||||
LOG.debug("Zone map to add: %s", zone_map)
|
||||
LOG.info(_LI("Zone map to add: %(zone_map)s"),
|
||||
{'zone_map': zone_map})
|
||||
LOG.info(_LI("Zone map to update add: %(zone_update_map)s"),
|
||||
{'zone_update_map': zone_update_map})
|
||||
if zone_map or zone_update_map:
|
||||
conn = None
|
||||
try:
|
||||
conn = importutils.import_object(
|
||||
@ -231,10 +242,19 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
password=fabric_pwd,
|
||||
port=fabric_port,
|
||||
vsan=zoning_vsan)
|
||||
conn.add_zones(
|
||||
zone_map, self.configuration.cisco_zone_activate,
|
||||
zoning_vsan, cfgmap_from_fabric,
|
||||
statusmap_from_fabric)
|
||||
if zone_map:
|
||||
conn.add_zones(
|
||||
zone_map,
|
||||
self.configuration.cisco_zone_activate,
|
||||
zoning_vsan, cfgmap_from_fabric,
|
||||
statusmap_from_fabric)
|
||||
if zone_update_map:
|
||||
conn.update_zones(
|
||||
zone_update_map,
|
||||
self.configuration.cisco_zone_activate,
|
||||
zoning_vsan, ZoneConstant.ZONE_ADD,
|
||||
cfgmap_from_fabric,
|
||||
statusmap_from_fabric)
|
||||
conn.cleanup()
|
||||
except exception.CiscoZoningCliException as cisco_ex:
|
||||
msg = _("Exception: %s") % six.text_type(cisco_ex)
|
||||
@ -244,6 +264,11 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
LOG.exception(msg)
|
||||
raise exception.FCZoneDriverException(msg)
|
||||
LOG.debug("Zones added successfully: %s", zone_map)
|
||||
else:
|
||||
LOG.debug("Zones already exist - Initiator Target Map: %s",
|
||||
initiator_target_map)
|
||||
else:
|
||||
LOG.debug("Zoning session exists VSAN: %s", zoning_vsan)
|
||||
|
||||
@lockutils.synchronized('cisco', 'fcfabric-', True)
|
||||
def delete_connection(self, fabric, initiator_target_map, host_name=None,
|
||||
@ -299,7 +324,7 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
for initiator_key in initiator_target_map.keys():
|
||||
initiator = initiator_key.lower()
|
||||
formatted_initiator = zm_utils.get_formatted_wwn(initiator)
|
||||
zone_map = {}
|
||||
zone_update_map = {}
|
||||
zones_to_delete = []
|
||||
t_list = initiator_target_map[initiator_key]
|
||||
if zoning_policy == 'initiator-target':
|
||||
@ -337,7 +362,7 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
storage_system,
|
||||
self.configuration.cisco_zone_name_prefix,
|
||||
SUPPORTED_CHARS)
|
||||
|
||||
# Check if there are zone members leftover after removal
|
||||
if (zone_names and (zone_name in zone_names)):
|
||||
filtered_members = filter(
|
||||
lambda x: x not in zone_members,
|
||||
@ -345,24 +370,30 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
|
||||
# The assumption here is that initiator is always
|
||||
# there in the zone as it is 'initiator' policy.
|
||||
# We find the filtered list and if it is non-empty,
|
||||
# add initiator to it and update zone if filtered
|
||||
# list is empty, we remove that zone.
|
||||
# If filtered list is empty, we remove that zone.
|
||||
# If there are other members leftover, then perform
|
||||
# update_zone to remove targets
|
||||
LOG.debug("Zone delete - I mode: filtered targets: %s",
|
||||
filtered_members)
|
||||
if filtered_members:
|
||||
filtered_members.append(formatted_initiator)
|
||||
LOG.debug("Filtered zone members to update: %s",
|
||||
filtered_members)
|
||||
zone_map[zone_name] = filtered_members
|
||||
LOG.debug("Filtered zone Map to update: %s",
|
||||
zone_map)
|
||||
remove_members = filter(
|
||||
lambda x: x in
|
||||
cfgmap_from_fabric['zones'][zone_name],
|
||||
zone_members)
|
||||
if remove_members:
|
||||
# Do not want to remove the initiator
|
||||
remove_members.remove(formatted_initiator)
|
||||
LOG.debug("Zone members to remove: %s",
|
||||
remove_members)
|
||||
zone_update_map[zone_name] = remove_members
|
||||
LOG.debug("Filtered zone Map to update: %s",
|
||||
zone_update_map)
|
||||
else:
|
||||
zones_to_delete.append(zone_name)
|
||||
else:
|
||||
LOG.info(_LI("Zoning Policy: %s, not recognized"),
|
||||
zoning_policy)
|
||||
LOG.debug("Final Zone map to update: %s", zone_map)
|
||||
LOG.debug("Zone map to remove update: %s", zone_update_map)
|
||||
LOG.debug("Final Zone list to delete: %s", zones_to_delete)
|
||||
conn = None
|
||||
try:
|
||||
@ -374,11 +405,12 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
port=fabric_port,
|
||||
vsan=zoning_vsan)
|
||||
# Update zone membership.
|
||||
if zone_map:
|
||||
conn.add_zones(
|
||||
zone_map, self.configuration.cisco_zone_activate,
|
||||
zoning_vsan, cfgmap_from_fabric,
|
||||
statusmap_from_fabric)
|
||||
if zone_update_map:
|
||||
conn.update_zones(
|
||||
zone_update_map,
|
||||
self.configuration.cisco_zone_activate,
|
||||
zoning_vsan, ZoneConstant.ZONE_REMOVE,
|
||||
cfgmap_from_fabric, statusmap_from_fabric)
|
||||
# Delete zones ~sk.
|
||||
if zones_to_delete:
|
||||
zone_name_string = ''
|
||||
@ -403,7 +435,7 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
|
||||
msg = _("Failed to update or delete zoning configuration")
|
||||
LOG.exception(msg)
|
||||
raise exception.FCZoneDriverException(msg)
|
||||
LOG.debug("Zones deleted successfully: %s", zone_map)
|
||||
LOG.debug("Zones deleted successfully: %s", zone_update_map)
|
||||
else:
|
||||
LOG.debug("Zoning session exists VSAN: %s", zoning_vsan)
|
||||
|
||||
|
@ -23,7 +23,8 @@ CFG_ZONESET = 'zoneset'
|
||||
CFG_ZONE = 'zone'
|
||||
CFG_ZONE_MEMBER = 'pwwn'
|
||||
CFG_ZONES = 'zones'
|
||||
|
||||
ZONE_ADD = 'zoneadd'
|
||||
ZONE_REMOVE = 'zoneremove'
|
||||
"""
|
||||
CLI Commands for FC zoning operations.
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user