diff --git a/cinder/tests/unit/zonemanager/test_driverutils.py b/cinder/tests/unit/zonemanager/test_driverutils.py new file mode 100644 index 00000000000..bb5d34dcf99 --- /dev/null +++ b/cinder/tests/unit/zonemanager/test_driverutils.py @@ -0,0 +1,113 @@ +# (c) Copyright 2015 Brocade Communications Systems Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + + +"""Unit tests for friendly zone name.""" +import ddt +import string + +from cinder import test +from cinder.zonemanager.drivers import driver_utils + +TEST_CHAR_SET = string.ascii_letters + string.digits + + +@ddt.ddt +class TestDriverUtils(test.TestCase): + + @ddt.data('OSHost10010008c7cff523b01AMCEArray20240002ac000a50') + def test_get_friendly_zone_name_valid_hostname_storagesystem(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", "OS_Host100", 'AMCE' + '_Array', "openstack", TEST_CHAR_SET)) + + @ddt.data('openstack10008c7cff523b0120240002ac000a50') + def test_get_friendly_zone_name_hostname_storagesystem_none(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", None, None, + "openstack", TEST_CHAR_SET)) + + @ddt.data('openstack10008c7cff523b0120240002ac000a50') + def test_get_friendly_zone_name_storagesystem_none(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", "OS_Host100", None, + "openstack", TEST_CHAR_SET)) + + @ddt.data('openstack10008c7cff523b0120240002ac000a50') + def test_get_friendly_zone_name_hostname_none(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", None, "AMCE_Array", + "openstack", TEST_CHAR_SET)) + + @ddt.data('OSHost10010008c7cff523b01') + def test_get_friendly_zone_name_initiator_mode(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator', "10:00:8c:7c:ff:52:3b:01", None, + "OS_Host100", None, "openstack", TEST_CHAR_SET)) + + @ddt.data('openstack10008c7cff523b01') + def test_get_friendly_zone_name_initiator_mode_hostname_none(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator', "10:00:8c:7c:ff:52:3b:01", None, + None, None, "openstack", TEST_CHAR_SET)) + + @ddt.data('OSHost100XXXX10008c7cff523b01AMCEArrayYYYY20240002ac000a50') + def test_get_friendly_zone_name_storagename_length_too_long(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", + "OS_Host100XXXXXXXXXX", + "AMCE_ArrayYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + "YYYY", "openstack", TEST_CHAR_SET)) + + @ddt.data('OSHost100XXXX10008c7cff523b01AMCEArrayYYYY20240002ac000a50') + def test_get_friendly_zone_name_max_length(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", + "OS_Host100XXXXXXXXXX", + "AMCE_ArrayYYYYYYYYYY", + "openstack", TEST_CHAR_SET)) + + @ddt.data('OSHost100XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX10008c7cff523b01') + def test_get_friendly_zone_name_initiator_mode_hostname_max_length(self, + value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator', "10:00:8c:7c:ff:52:3b:01", None, + 'OS_Host100XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + 'XXXXX', + None, "openstack", TEST_CHAR_SET)) + + @ddt.data('openstack110008c7cff523b0120240002ac000a50') + def test_get_friendly_zone_name_invalid_characters(self, value): + self.assertEqual(value, + driver_utils.get_friendly_zone_name( + 'initiator-target', "10:00:8c:7c:ff:52:3b:01", + "20:24:00:02:ac:00:0a:50", None, "AMCE_Array", + "open-stack*1_", TEST_CHAR_SET)) diff --git a/cinder/tests/unit/zonemanager/test_fc_zone_manager.py b/cinder/tests/unit/zonemanager/test_fc_zone_manager.py index 6ec6d93a24d..c2072941a3f 100644 --- a/cinder/tests/unit/zonemanager/test_fc_zone_manager.py +++ b/cinder/tests/unit/zonemanager/test_fc_zone_manager.py @@ -29,6 +29,17 @@ from cinder.zonemanager import fc_zone_manager fabric_name = 'BRCD_FAB_3' init_target_map = {'10008c7cff523b01': ['20240002ac000a50']} +conn_info = { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'target_discovered': True, + 'target_lun': 1, + 'target_wwn': '20240002ac000a50', + 'initiator_target_map': { + '10008c7cff523b01': ['20240002ac000a50'] + } + } +} fabric_map = {'BRCD_FAB_3': ['20240002ac000a50']} target_list = ['20240002ac000a50'] @@ -60,10 +71,12 @@ class TestFCZoneManager(test.TestCase): with mock.patch.object(self.zm.driver, 'add_connection')\ as add_connection_mock: self.zm.driver.get_san_context.return_value = fabric_map - self.zm.add_connection(init_target_map) + self.zm.add_connection(conn_info) self.zm.driver.get_san_context.assert_called_once_with(target_list) add_connection_mock.assert_called_once_with(fabric_name, - init_target_map) + init_target_map, + None, + None) @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_add_connection_error(self, opt_mock): @@ -71,17 +84,19 @@ class TestFCZoneManager(test.TestCase): as add_connection_mock: add_connection_mock.side_effect = exception.FCZoneDriverException self.assertRaises(exception.ZoneManagerException, - self.zm.add_connection, init_target_map) + self.zm.add_connection, conn_info) @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_delete_connection(self, opt_mock): with mock.patch.object(self.zm.driver, 'delete_connection')\ as delete_connection_mock: self.zm.driver.get_san_context.return_value = fabric_map - self.zm.delete_connection(init_target_map) + self.zm.delete_connection(conn_info) self.zm.driver.get_san_context.assert_called_once_with(target_list) delete_connection_mock.assert_called_once_with(fabric_name, - init_target_map) + init_target_map, + None, + None) @mock.patch('oslo_config.cfg._is_opt_registered', return_value=False) def test_delete_connection_error(self, opt_mock): @@ -89,4 +104,4 @@ class TestFCZoneManager(test.TestCase): as del_connection_mock: del_connection_mock.side_effect = exception.FCZoneDriverException self.assertRaises(exception.ZoneManagerException, - self.zm.delete_connection, init_target_map) + self.zm.delete_connection, conn_info) diff --git a/cinder/tests/unit/zonemanager/test_volume_driver.py b/cinder/tests/unit/zonemanager/test_volume_driver.py index f68f39defc3..fb7e296c122 100644 --- a/cinder/tests/unit/zonemanager/test_volume_driver.py +++ b/cinder/tests/unit/zonemanager/test_volume_driver.py @@ -53,8 +53,7 @@ class TestVolumeDriver(test.TestCase): as mock_safe_get: mock_safe_get.return_value = 'fabric' conn_info = self.driver.initialize_connection(None, None) - init_target_map = conn_info['data']['initiator_target_map'] - add_zone_mock.assert_called_once_with(init_target_map) + add_zone_mock.assert_called_once_with(conn_info) @mock.patch.object(utils, 'require_driver_initialized') def test_initialize_connection_no_decorator(self, utils_mock): @@ -77,8 +76,7 @@ class TestVolumeDriver(test.TestCase): as mock_safe_get: mock_safe_get.return_value = 'fabric' conn_info = self.driver.terminate_connection(None, None) - init_target_map = conn_info['data']['initiator_target_map'] - remove_zone_mock.assert_called_once_with(init_target_map) + remove_zone_mock.assert_called_once_with(conn_info) @mock.patch.object(utils, 'require_driver_initialized') def test_terminate_connection_no_decorator(self, utils_mock): diff --git a/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py b/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py index 1328bb1ba77..c6bb348273c 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py +++ b/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py @@ -41,6 +41,7 @@ brcd_zone_opts = [ default=True, help='overridden zoning activation state'), cfg.StrOpt('zone_name_prefix', + default='openstack', help='overridden zone name prefix'), cfg.StrOpt('principal_switch_wwn', help='Principal switch WWN of the fabric'), @@ -55,7 +56,8 @@ def load_fabric_configurations(fabric_names): fabric_configs = {} for fabric_name in fabric_names: config = configuration.Configuration(brcd_zone_opts, fabric_name) - LOG.debug("Loaded FC fabric config %s", fabric_name) + LOG.debug("Loaded FC fabric config %(fabricname)s", + {'fabricname': fabric_name}) fabric_configs[fabric_name] = config return fabric_configs diff --git a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py index e865ea0b833..93b143855ea 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py +++ b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py @@ -36,14 +36,17 @@ from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import importutils import six +import string from cinder import exception -from cinder.i18n import _, _LE, _LI +from cinder.i18n import _, _LE, _LI, _LW from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as fabric_opts +from cinder.zonemanager.drivers import driver_utils from cinder.zonemanager.drivers import fc_zone_driver LOG = logging.getLogger(__name__) +SUPPORTED_CHARS = string.ascii_letters + string.digits + '_' brcd_opts = [ cfg.StrOpt('brcd_sb_connector', default='cinder.zonemanager.drivers.brocade' @@ -64,9 +67,10 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): Version history: 1.0 - Initial Brocade FC zone driver 1.1 - Implements performance enhancements + 1.2 - Added support for friendly zone name """ - VERSION = "1.1" + VERSION = "1.2" def __init__(self, **kwargs): super(BrcdFCZoneDriver, self).__init__(**kwargs) @@ -74,8 +78,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): self.configuration = kwargs.get('configuration', None) if self.configuration: self.configuration.append_config_values(brcd_opts) - # Adding a hack to hendle parameters from super classes - # in case configured with multi backend. + # Adding a hack to handle parameters from super classes + # in case configured with multiple back ends. fabric_names = self.configuration.safe_get('fc_fabric_names') base_san_opts = [] if not fabric_names: @@ -109,7 +113,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): [wwn_str[i:i + 2] for i in range(0, len(wwn_str), 2)]) @lockutils.synchronized('brcd', 'fcfabric-', True) - def add_connection(self, fabric, initiator_target_map): + def add_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Concrete implementation of add_connection. Based on zoning policy and state of each I-T pair, list of zone @@ -121,22 +126,27 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): :param fabric: Fabric name from cinder.conf file :param initiator_target_map: Mapping of initiator to list of targets """ - LOG.debug("Add connection for Fabric: %s", fabric) - LOG.info(_LI("BrcdFCZoneDriver - Add connection " - "for I-T map: %s"), initiator_target_map) + LOG.info(_LI("BrcdFCZoneDriver - Add connection for fabric " + "%(fabric)s for I-T map: %(i_t_map)s"), + {'fabric': fabric, + 'i_t_map': initiator_target_map}) zoning_policy = self.configuration.zoning_policy zoning_policy_fab = self.fabric_configs[fabric].safe_get( 'zoning_policy') - if zoning_policy_fab: - zoning_policy = zoning_policy_fab zone_name_prefix = self.fabric_configs[fabric].safe_get( 'zone_name_prefix') - if not zone_name_prefix: - zone_name_prefix = 'openstack' zone_activate = self.fabric_configs[fabric].safe_get( 'zone_activate') + if zoning_policy_fab: + zoning_policy = zoning_policy_fab + LOG.info(_LI("Zoning policy for Fabric %(policy)s"), + {'policy': zoning_policy}) + if (zoning_policy != 'initiator' + and zoning_policy != 'initiator-target'): + LOG.info(_LI("Zoning policy is not valid, " + "no zoning will be performed.")) + return - LOG.info(_LI("Zoning policy for Fabric %s"), zoning_policy) cli_client = self._get_cli_client(fabric) cfgmap_from_fabric = self._get_active_zone_set(cli_client) @@ -154,24 +164,37 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): target = t.lower() zone_members = [self.get_formatted_wwn(initiator), self.get_formatted_wwn(target)] - zone_name = (zone_name_prefix - + initiator.replace(':', '') - + target.replace(':', '')) + zone_name = driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + zone_name_prefix, + SUPPORTED_CHARS) if ( len(cfgmap_from_fabric) == 0 or ( zone_name not in zone_names)): zone_map[zone_name] = zone_members else: # This is I-T zoning, skip if zone already exists. - LOG.info(_LI("Zone exists in I-T mode. " - "Skipping zone creation %s"), zone_name) + LOG.info(_LI("Zone exists in I-T mode. Skipping " + "zone creation for %(zonename)s"), + {'zonename': zone_name}) elif zoning_policy == 'initiator': zone_members = [self.get_formatted_wwn(initiator)] for t in t_list: target = t.lower() zone_members.append(self.get_formatted_wwn(target)) - zone_name = zone_name_prefix + initiator.replace(':', '') + zone_name = driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + zone_name_prefix, + SUPPORTED_CHARS) if len(zone_names) > 0 and (zone_name in zone_names): zone_members = zone_members + filter( @@ -179,13 +202,9 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): cfgmap_from_fabric['zones'][zone_name]) zone_map[zone_name] = zone_members - else: - msg = _("Zoning Policy: %s, not " - "recognized") % zoning_policy - LOG.error(msg) - raise exception.FCZoneDriverException(msg) - LOG.info(_LI("Zone map to add: %s"), zone_map) + LOG.info(_LI("Zone map to add: %(zonemap)s"), + {'zonemap': zone_map}) if len(zone_map) > 0: try: @@ -199,10 +218,12 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): msg = _("Failed to add zoning configuration.") LOG.exception(msg) raise exception.FCZoneDriverException(msg) - LOG.debug("Zones added successfully: %s", zone_map) + LOG.debug("Zones added successfully: %(zonemap)s", + {'zonemap': zone_map}) @lockutils.synchronized('brcd', 'fcfabric-', True) - def delete_connection(self, fabric, initiator_target_map): + def delete_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Concrete implementation of delete_connection. Based on zoning policy and state of each I-T pair, list of zones @@ -212,22 +233,21 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): :param fabric: Fabric name from cinder.conf file :param initiator_target_map: Mapping of initiator to list of targets """ - LOG.debug("Delete connection for fabric: %s", fabric) - LOG.info(_LI("BrcdFCZoneDriver - Delete connection for I-T map: %s"), - initiator_target_map) + LOG.info(_LI("BrcdFCZoneDriver - Delete connection for fabric " + "%(fabric)s for I-T map: %(i_t_map)s"), + {'fabric': fabric, + 'i_t_map': initiator_target_map}) zoning_policy = self.configuration.zoning_policy zoning_policy_fab = self.fabric_configs[fabric].safe_get( 'zoning_policy') - if zoning_policy_fab: - zoning_policy = zoning_policy_fab zone_name_prefix = self.fabric_configs[fabric].safe_get( 'zone_name_prefix') - if not zone_name_prefix: - zone_name_prefix = 'openstack' zone_activate = self.fabric_configs[fabric].safe_get( 'zone_activate') - - LOG.info(_LI("Zoning policy for fabric %s"), zoning_policy) + if zoning_policy_fab: + zoning_policy = zoning_policy_fab + LOG.info(_LI("Zoning policy for fabric %(policy)s"), + {'policy': zoning_policy}) conn = self._get_cli_client(fabric) cfgmap_from_fabric = self._get_active_zone_set(conn) @@ -238,7 +258,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): # Based on zoning policy, get zone member list and push changes to # fabric. This operation could result in an update for zone config # with new member list or deleting zones from active cfg. - LOG.debug("zone config from Fabric: %s", cfgmap_from_fabric) + LOG.debug("zone config from Fabric: %(cfgmap)s", + {'cfgmap': cfgmap_from_fabric}) for initiator_key in initiator_target_map.keys(): initiator = initiator_key.lower() formatted_initiator = self.get_formatted_wwn(initiator) @@ -249,15 +270,20 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): # In this case, zone needs to be deleted. for t in t_list: target = t.lower() - zone_name = ( - zone_name_prefix - + initiator.replace(':', '') - + target.replace(':', '')) - LOG.debug("Zone name to del: %s", zone_name) + zone_name = driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + zone_name_prefix, + SUPPORTED_CHARS) + LOG.debug("Zone name to delete: %(zonename)s", + {'zonename': zone_name}) if len(zone_names) > 0 and (zone_name in zone_names): # delete zone. - LOG.debug("Added zone to delete to " - "list: %s", zone_name) + LOG.debug("Added zone to delete to list: %(zonename)s", + {'zonename': zone_name}) zones_to_delete.append(zone_name) elif zoning_policy == 'initiator': @@ -266,7 +292,14 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): target = t.lower() zone_members.append(self.get_formatted_wwn(target)) - zone_name = zone_name_prefix + initiator.replace(':', '') + zone_name = driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + zone_name_prefix, + SUPPORTED_CHARS) if (zone_names and (zone_name in zone_names)): filtered_members = filter( @@ -278,22 +311,25 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): # filtered list and if it is non-empty, add initiator # to it and update zone if filtered list is empty, we # remove that zone. - LOG.debug("Zone delete - I mode: " - "filtered targets: %s", filtered_members) + LOG.debug("Zone delete - initiator mode: " + "filtered targets: %(targets)s", + {'targets': filtered_members}) if filtered_members: filtered_members.append(formatted_initiator) - LOG.debug("Filtered zone members to " - "update: %s", filtered_members) + LOG.debug("Filtered zone members to update: " + "%(members)s", {'members': filtered_members}) zone_map[zone_name] = filtered_members - LOG.debug("Filtered zone Map to " - "update: %s", zone_map) + LOG.debug("Filtered zone map to update: %(zonemap)s", + {'zonemap': zone_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("Final Zone list to delete: %s", zones_to_delete) + LOG.warning(_LW("Zoning policy not recognized: %(policy)s"), + {'policy': zoning_policy}) + LOG.debug("Final zone map to update: %(zonemap)s", + {'zonemap': zone_map}) + LOG.debug("Final zone list to delete: %(zones)s", + {'zones': zones_to_delete}) try: # Update zone membership. if zone_map: @@ -333,13 +369,14 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): fabric_map = {} fc_fabric_names = self.configuration.fc_fabric_names fabrics = [x.strip() for x in fc_fabric_names.split(',')] - LOG.debug("Fabric List: %s", fabrics) - LOG.debug("Target wwn List: %s", target_wwn_list) + LOG.debug("Fabric List: %(fabrics)s", {'fabrics': fabrics}) + LOG.debug("Target WWN list: %(targetwwns)s", + {'targetwwns': target_wwn_list}) if len(fabrics) > 0: for t in target_wwn_list: formatted_target_list.append(self.get_formatted_wwn(t.lower())) - LOG.debug("Formatted Target wwn List:" - " %s", formatted_target_list) + LOG.debug("Formatted target WWN list: %(targetlist)s", + {'targetlist': formatted_target_list}) for fabric_name in fabrics: conn = self._get_cli_client(fabric_name) @@ -348,7 +385,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): nsinfo = None try: nsinfo = conn.get_nameserver_info() - LOG.debug("name server info from fabric: %s", nsinfo) + LOG.debug("Name server info from fabric: %(nsinfo)s", + {'nsinfo': nsinfo}) conn.cleanup() except exception.BrocadeZoningCliException: if not conn.is_supported_firmware(): @@ -368,17 +406,19 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): nsinfo) if visible_targets: - LOG.info(_LI("Filtered targets for SAN is: %s"), - {fabric_name: visible_targets}) + LOG.info(_LI("Filtered targets for SAN is: %(targets)s"), + {'targets': visible_targets}) # getting rid of the ':' before returning for idx, elem in enumerate(visible_targets): visible_targets[idx] = str( visible_targets[idx]).replace(':', '') fabric_map[fabric_name] = visible_targets else: - LOG.debug("No targets are in the nameserver for SAN %s", - fabric_name) - LOG.debug("Return SAN context output: %s", fabric_map) + LOG.debug("No targets found in the nameserver " + "for fabric: %(fabric)s", + {'fabric': fabric_name}) + LOG.debug("Return SAN context output: %(fabricmap)s", + {'fabricmap': fabric_map}) return fabric_map def _get_active_zone_set(self, conn): @@ -396,7 +436,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): msg = (_("Failed to retrieve active zoning configuration %s") % six.text_type(e)) raise exception.FCZoneDriverException(msg) - LOG.debug("Active zone set from fabric: %s", cfgmap) + LOG.debug("Active zone set from fabric: %(cfgmap)s", + {'cfgmap': cfgmap}) return cfgmap def _get_cli_client(self, fabric): @@ -408,7 +449,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver): try: cli_client = self.sb_conn_map.get(fabric_ip) if not cli_client: - LOG.debug("CLI client not found, creating for %s", fabric_ip) + LOG.debug("CLI client not found, creating for %(ip)s", + {'ip': fabric_ip}) cli_client = importutils.import_object( self.configuration.brcd_sb_connector, ipaddress=fabric_ip, diff --git a/cinder/zonemanager/drivers/brocade/fc_zone_constants.py b/cinder/zonemanager/drivers/brocade/fc_zone_constants.py index 3ef01cde11f..1cc2372e583 100644 --- a/cinder/zonemanager/drivers/brocade/fc_zone_constants.py +++ b/cinder/zonemanager/drivers/brocade/fc_zone_constants.py @@ -16,7 +16,6 @@ # under the License. # - """ Common constants used by Brocade FC Zone Driver. """ diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py index b94733ede4b..597d66b2b3e 100644 --- a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py +++ b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py @@ -33,15 +33,18 @@ from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import importutils import six +import string from cinder import exception from cinder.i18n import _, _LE, _LI from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as fabric_opts +from cinder.zonemanager.drivers import driver_utils from cinder.zonemanager.drivers import fc_zone_driver from cinder.zonemanager import utils as zm_utils LOG = logging.getLogger(__name__) +SUPPORTED_CHARS = string.ascii_letters + string.digits + '$' + '-' + '^' + '_' cisco_opts = [ cfg.StrOpt('cisco_sb_connector', default='cinder.zonemanager.drivers.cisco' @@ -61,9 +64,10 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): Version history: 1.0 - Initial Cisco FC zone driver + 1.1 - Added friendly zone name support """ - VERSION = "1.0.0" + VERSION = "1.1.0" def __init__(self, **kwargs): super(CiscoFCZoneDriver, self).__init__(**kwargs) @@ -109,7 +113,8 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): fabric_names) @lockutils.synchronized('cisco', 'fcfabric-', True) - def add_connection(self, fabric, initiator_target_map): + def add_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Concrete implementation of add_connection. Based on zoning policy and state of each I-T pair, list of zone @@ -165,10 +170,15 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): zone_members = [ zm_utils.get_formatted_wwn(initiator), zm_utils.get_formatted_wwn(target)] - zone_name = (self. - configuration.cisco_zone_name_prefix - + initiator.replace(':', '') - + target.replace(':', '')) + zone_name = ( + driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + self.configuration.cisco_zone_name_prefix, + SUPPORTED_CHARS)) if (len(cfgmap_from_fabric) == 0 or ( zone_name not in zone_names)): zone_map[zone_name] = zone_members @@ -185,8 +195,15 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): zone_members.append( zm_utils.get_formatted_wwn(target)) - zone_name = self.configuration.cisco_zone_name_prefix \ - + initiator.replace(':', '') + zone_name = ( + driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + self.configuration.cisco_zone_name_prefix, + SUPPORTED_CHARS)) if len(zone_names) > 0 and (zone_name in zone_names): zone_members = zone_members + filter( @@ -228,7 +245,8 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): LOG.debug("Zoning session exists VSAN: %s", zoning_vsan) @lockutils.synchronized('cisco', 'fcfabric-', True) - def delete_connection(self, fabric, initiator_target_map): + def delete_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Concrete implementation of delete_connection. Based on zoning policy and state of each I-T pair, list of zones @@ -288,9 +306,14 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): for t in t_list: target = t.lower() zone_name = ( - self.configuration.cisco_zone_name_prefix - + initiator.replace(':', '') - + target.replace(':', '')) + driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + self.configuration.cisco_zone_name_prefix, + SUPPORTED_CHARS)) LOG.debug("Zone name to del: %s", zone_name) if (len(zone_names) > 0 and (zone_name in zone_names)): # delete zone. @@ -305,8 +328,14 @@ class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver): zone_members.append( zm_utils.get_formatted_wwn(target)) - zone_name = self.configuration.cisco_zone_name_prefix \ - + initiator.replace(':', '') + zone_name = driver_utils.get_friendly_zone_name( + zoning_policy, + initiator, + target, + host_name, + storage_system, + self.configuration.cisco_zone_name_prefix, + SUPPORTED_CHARS) if (zone_names and (zone_name in zone_names)): filtered_members = filter( diff --git a/cinder/zonemanager/drivers/driver_utils.py b/cinder/zonemanager/drivers/driver_utils.py new file mode 100644 index 00000000000..51ab0b65b7a --- /dev/null +++ b/cinder/zonemanager/drivers/driver_utils.py @@ -0,0 +1,79 @@ +# (c) Copyright 2014 Brocade Communications Systems Inc. +# All Rights Reserved. +# +# Copyright 2014 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import re + +from oslo_log import log + +from cinder.i18n import _LI + +LOG = log.getLogger(__name__) + + +def get_friendly_zone_name(zoning_policy, initiator, target, + host_name, storage_system, zone_name_prefix, + supported_chars): + """Utility function implementation of _get_friendly_zone_name. + + Get friendly zone name is used to form the zone name + based on the details provided by the caller + + :param zoning_policy - determines the zoning policy is either + initiator-target or initiator + :param initiator - initiator WWN + :param target - target WWN + :param host_name - Host name returned from Volume Driver + :param storage_system - Storage name returned from Volume Driver + :param zone_name_prefix - user defined zone prefix configured + in cinder.conf + :param supported_chars - Supported character set of FC switch vendor. + Example: 'abc123_-$'. These are defined in the FC zone drivers. + """ + if host_name is None: + host_name = '' + if storage_system is None: + storage_system = '' + if zoning_policy == 'initiator-target': + host_name = host_name[:14] + storage_system = storage_system[:14] + if len(host_name) > 0 and len(storage_system) > 0: + zone_name = (host_name + "_" + + initiator.replace(':', '') + "_" + + storage_system + "_" + + target.replace(':', '')) + else: + zone_name = (zone_name_prefix + + initiator.replace(':', '') + + target.replace(':', '')) + LOG.info(_LI("Zone name created using prefix because either " + "host name or storage system is none.")) + else: + host_name = host_name[:47] + if len(host_name) > 0: + zone_name = (host_name + "_" + + initiator.replace(':', '')) + else: + zone_name = (zone_name_prefix + + initiator.replace(':', '')) + LOG.info(_LI("Zone name created using prefix because host " + "name is none.")) + + LOG.info(_LI("Friendly zone name after forming: %(zonename)s"), + {'zonename': zone_name}) + zone_name = re.sub('[^%s]' % supported_chars, '', zone_name) + return zone_name diff --git a/cinder/zonemanager/drivers/fc_zone_driver.py b/cinder/zonemanager/drivers/fc_zone_driver.py index 8bf7845f8bc..ec8f3e5a06f 100644 --- a/cinder/zonemanager/drivers/fc_zone_driver.py +++ b/cinder/zonemanager/drivers/fc_zone_driver.py @@ -43,7 +43,8 @@ class FCZoneDriver(fc_common.FCCommon): super(FCZoneDriver, self).__init__(**kwargs) LOG.debug("Initializing FCZoneDriver") - def add_connection(self, fabric, initiator_target_map): + def add_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Add connection control. Abstract method to add connection control. @@ -60,7 +61,8 @@ class FCZoneDriver(fc_common.FCCommon): """ raise NotImplementedError() - def delete_connection(self, fabric, initiator_target_map): + def delete_connection(self, fabric, initiator_target_map, host_name=None, + storage_system=None): """Delete connection control. Abstract method to remove connection control. diff --git a/cinder/zonemanager/fc_zone_manager.py b/cinder/zonemanager/fc_zone_manager.py index 2d598b5fc6d..096fd78a64a 100644 --- a/cinder/zonemanager/fc_zone_manager.py +++ b/cinder/zonemanager/fc_zone_manager.py @@ -40,6 +40,8 @@ from cinder import exception from cinder.i18n import _, _LI from cinder.volume import configuration as config from cinder.zonemanager import fc_common +import cinder.zonemanager.fczm_constants as zone_constant + LOG = logging.getLogger(__name__) @@ -59,7 +61,7 @@ zone_manager_opts = [ cfg.StrOpt('fc_san_lookup_service', default='cinder.zonemanager.drivers.brocade' '.brcd_fc_san_lookup_service.BrcdFCSanLookupService', - help='FC SAN Lookup Service'), + help='FC SAN Lookup Service') ] CONF = cfg.CONF @@ -67,15 +69,17 @@ CONF.register_opts(zone_manager_opts, group='fc-zone-manager') class ZoneManager(fc_common.FCCommon): + """Manages Connection control during attach/detach. Version History: 1.0 - Initial version 1.0.1 - Added __new__ for singleton + 1.0.2 - Added friendly zone name """ - VERSION = "1.0.1" + VERSION = "1.0.2" driver = None fabric_names = [] @@ -90,17 +94,18 @@ class ZoneManager(fc_common.FCCommon): self.configuration = config.Configuration(zone_manager_opts, 'fc-zone-manager') - self._build_driver() def _build_driver(self): zone_driver = self.configuration.zone_driver - LOG.debug("Zone Driver from config: {%s}", zone_driver) + LOG.debug("Zone driver from config: %(driver)s", + {'driver': zone_driver}) + zm_config = config.Configuration(zone_manager_opts, 'fc-zone-manager') # Initialize vendor specific implementation of FCZoneDriver self.driver = importutils.import_object( zone_driver, - configuration=self.configuration) + configuration=zm_config) def get_zoning_state_ref_count(self, initiator_wwn, target_wwn): """Zone management state check. @@ -113,7 +118,7 @@ class ZoneManager(fc_common.FCCommon): # check the state for I-T pair return count - def add_connection(self, initiator_target_map): + def add_connection(self, conn_info): """Add connection control. Adds connection control for the given initiator target map. @@ -125,14 +130,33 @@ class ZoneManager(fc_common.FCCommon): } """ connected_fabric = None + host_name = None + storage_system = None + try: + initiator_target_map = ( + conn_info[zone_constant.DATA][zone_constant.IT_MAP]) + + if zone_constant.HOST in conn_info[zone_constant.DATA]: + host_name = conn_info[ + zone_constant.DATA][ + zone_constant.HOST].replace(" ", "_") + + if zone_constant.STORAGE in conn_info[zone_constant.DATA]: + storage_system = ( + conn_info[ + zone_constant.DATA][ + zone_constant.STORAGE].replace(" ", "_")) + for initiator in initiator_target_map.keys(): target_list = initiator_target_map[initiator] - LOG.debug("Target List: %s", target_list) + LOG.debug("Target list : %(targets)s", + {'targets': target_list}) # get SAN context for the target list fabric_map = self.get_san_context(target_list) - LOG.debug("Fabric Map after context lookup: %s", fabric_map) + LOG.debug("Fabric map after context lookup: %(fabricmap)s", + {'fabricmap': fabric_map}) # iterate over each SAN and apply connection control for fabric in fabric_map.keys(): connected_fabric = fabric @@ -141,13 +165,14 @@ class ZoneManager(fc_common.FCCommon): i_t_map = {initiator: t_list} valid_i_t_map = self.get_valid_initiator_target_map( i_t_map, True) - LOG.info(_LI("Final filtered map for fabric: %s"), - valid_i_t_map) + LOG.info(_LI("Final filtered map for fabric: %(i_t_map)s"), + {'i_t_map': valid_i_t_map}) # Call driver to add connection control - self.driver.add_connection(fabric, valid_i_t_map) + self.driver.add_connection(fabric, valid_i_t_map, + host_name, storage_system) - LOG.info(_LI("Add Connection: Finished iterating " + LOG.info(_LI("Add connection: finished iterating " "over all target list")) except Exception as e: msg = _("Failed adding connection for fabric=%(fabric)s: " @@ -156,7 +181,7 @@ class ZoneManager(fc_common.FCCommon): LOG.error(msg) raise exception.ZoneManagerException(reason=msg) - def delete_connection(self, initiator_target_map): + def delete_connection(self, conn_info): """Delete connection. Updates/deletes connection control for the given initiator target map. @@ -168,16 +193,31 @@ class ZoneManager(fc_common.FCCommon): } """ connected_fabric = None + host_name = None + storage_system = None + try: + initiator_target_map = ( + conn_info[zone_constant.DATA][zone_constant.IT_MAP]) + + if zone_constant.HOST in conn_info[zone_constant.DATA]: + host_name = conn_info[zone_constant.DATA][zone_constant.HOST] + + if zone_constant.STORAGE in conn_info[zone_constant.DATA]: + storage_system = ( + conn_info[ + zone_constant.DATA][ + zone_constant.STORAGE].replace(" ", "_")) + for initiator in initiator_target_map.keys(): target_list = initiator_target_map[initiator] - LOG.info(_LI("Delete connection Target List: %s"), - target_list) + LOG.info(_LI("Delete connection target list: %(targets)s"), + {'targets': target_list}) # get SAN context for the target list fabric_map = self.get_san_context(target_list) - LOG.debug("Delete connection Fabric Map from SAN " - "context: %s", fabric_map) + LOG.debug("Delete connection fabric map from SAN " + "context: %(fabricmap)s", {'fabricmap': fabric_map}) # iterate over each SAN and apply connection control for fabric in fabric_map.keys(): @@ -187,14 +227,17 @@ class ZoneManager(fc_common.FCCommon): i_t_map = {initiator: t_list} valid_i_t_map = self.get_valid_initiator_target_map( i_t_map, False) - LOG.info(_LI("Final filtered map for delete " - "connection: %s"), valid_i_t_map) + LOG.info(_LI("Final filtered map for delete connection: " + "%(i_t_map)s"), {'i_t_map': valid_i_t_map}) # Call driver to delete connection control if len(valid_i_t_map) > 0: - self.driver.delete_connection(fabric, valid_i_t_map) + self.driver.delete_connection(fabric, + valid_i_t_map, + host_name, + storage_system) - LOG.debug("Delete Connection - Finished iterating over all" + LOG.debug("Delete connection - finished iterating over all" " target list") except Exception as e: msg = _("Failed removing connection for fabric=%(fabric)s: " @@ -210,7 +253,7 @@ class ZoneManager(fc_common.FCCommon): to list of target WWNs visible to the fabric. """ fabric_map = self.driver.get_san_context(target_wwn_list) - LOG.debug("Got SAN context: %s", fabric_map) + LOG.debug("Got SAN context: %(fabricmap)s", {'fabricmap': fabric_map}) return fabric_map def get_valid_initiator_target_map(self, initiator_target_map, @@ -239,5 +282,6 @@ class ZoneManager(fc_common.FCCommon): filtered_i_t_map[initiator] = t_list else: LOG.info(_LI("No targets to add or remove connection for " - "I: %s"), initiator) + "initiator: %(init_wwn)s"), + {'init_wwn': initiator}) return filtered_i_t_map diff --git a/cinder/zonemanager/fczm_constants.py b/cinder/zonemanager/fczm_constants.py new file mode 100644 index 00000000000..d1af9055f4d --- /dev/null +++ b/cinder/zonemanager/fczm_constants.py @@ -0,0 +1,22 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +""" +Common constants used by FC Zone Manager. +""" +IT_MAP = 'initiator_target_map' +DATA = 'data' +HOST = 'host_name' +STORAGE = 'storage_system' +SYSTEM = 'system' diff --git a/cinder/zonemanager/utils.py b/cinder/zonemanager/utils.py index b359b215a64..9a3c055d633 100644 --- a/cinder/zonemanager/utils.py +++ b/cinder/zonemanager/utils.py @@ -34,7 +34,7 @@ LOG.logger.setLevel(logging.DEBUG) def create_zone_manager(): """If zoning is enabled, build the Zone Manager.""" config = configuration.Configuration(manager.volume_manager_opts) - LOG.debug("Zoning mode: %s", config.safe_get('zoning_mode')) + LOG.debug("Zoning mode: %s.", config.safe_get('zoning_mode')) if config.safe_get('zoning_mode') == 'fabric': LOG.debug("FC Zone Manager enabled.") zm = fc_zone_manager.ZoneManager() @@ -51,11 +51,11 @@ def create_zone_manager(): def create_lookup_service(): config = configuration.Configuration(manager.volume_manager_opts) - LOG.debug("Zoning mode: %s", config.safe_get('zoning_mode')) + LOG.debug("Zoning mode: %s.", config.safe_get('zoning_mode')) if config.safe_get('zoning_mode') == 'fabric': LOG.debug("FC Lookup Service enabled.") lookup = fc_san_lookup_service.FCSanLookupService() - LOG.info(_LI("Using FC lookup service %s"), lookup.lookup_service) + LOG.info(_LI("Using FC lookup service %s."), lookup.lookup_service) return lookup else: LOG.debug("FC Lookup Service not enabled in cinder.conf.") @@ -73,6 +73,7 @@ def get_formatted_wwn(wwn_str): def AddFCZone(initialize_connection): """Decorator to add a FC Zone.""" + def decorator(self, *args, **kwargs): conn_info = initialize_connection(self, *args, **kwargs) if not conn_info: @@ -82,14 +83,12 @@ def AddFCZone(initialize_connection): vol_type = conn_info.get('driver_volume_type', None) if vol_type == 'fibre_channel': - if 'initiator_target_map' in conn_info['data']: - init_target_map = conn_info['data']['initiator_target_map'] zm = create_zone_manager() if zm: - LOG.debug("Add FC Zone for mapping '%s'.", - init_target_map) - zm.add_connection(init_target_map) + LOG.debug("AddFCZone connection info: %(conninfo)s.", + {'conninfo': conn_info}) + zm.add_connection(conn_info) return conn_info @@ -98,6 +97,7 @@ def AddFCZone(initialize_connection): def RemoveFCZone(terminate_connection): """Decorator for FC drivers to remove zone.""" + def decorator(self, *args, **kwargs): conn_info = terminate_connection(self, *args, **kwargs) if not conn_info: @@ -107,14 +107,12 @@ def RemoveFCZone(terminate_connection): vol_type = conn_info.get('driver_volume_type', None) if vol_type == 'fibre_channel': - if 'initiator_target_map' in conn_info['data']: - init_target_map = conn_info['data']['initiator_target_map'] zm = create_zone_manager() if zm: - LOG.debug("Remove FC Zone for mapping '%s'.", - init_target_map) - zm.delete_connection(init_target_map) + LOG.debug("RemoveFCZone connection info: %(conninfo)s.", + {'conninfo': conn_info}) + zm.delete_connection(conn_info) return conn_info diff --git a/releasenotes/notes/friendly-zone-names-d5e131d356040de0.yaml b/releasenotes/notes/friendly-zone-names-d5e131d356040de0.yaml new file mode 100644 index 00000000000..ffba6562f95 --- /dev/null +++ b/releasenotes/notes/friendly-zone-names-d5e131d356040de0.yaml @@ -0,0 +1,10 @@ +--- +features: + - Cinder FC Zone Manager Friendly Zone Names + This feature adds support for Fibre Channel user + friendly zone names if implemented by the volume driver. + If the volume driver passes the host name and + storage system to the Fibre Channel Zone Manager + in the conn_info structure, the zone manager + will use these names in structuring the zone + name to provide a user friendly zone name.