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:
Yucong Feng 2016-08-16 22:05:18 +02:00
parent a4ac6a98d1
commit 27b7a2907b
4 changed files with 163 additions and 54 deletions

View File

@ -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 = []

View File

@ -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."""

View File

@ -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)

View File

@ -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.
"""