diff --git a/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py b/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py index eedd63a3a78..de0137c2da0 100644 --- a/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py +++ b/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py @@ -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 = [] diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py index c1a407cafb4..1bf5f7d2707 100644 --- a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py +++ b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py @@ -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.""" diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py index 56ddaa1b526..607ac1a9717 100644 --- a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py +++ b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py @@ -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) diff --git a/cinder/zonemanager/drivers/cisco/fc_zone_constants.py b/cinder/zonemanager/drivers/cisco/fc_zone_constants.py index 6e1a8755c02..6182f80ed33 100644 --- a/cinder/zonemanager/drivers/cisco/fc_zone_constants.py +++ b/cinder/zonemanager/drivers/cisco/fc_zone_constants.py @@ -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. """