Browse Source

Adds friendly zone name support

Added user friendly names for zones to include host and storage
names along with ids to easily identify the host and storage port
details.

This is done by extracting the host and storage name from connection
info object in the zone manager. The host and storage names are passed
to the zone drivers to form a friendly name. The method signature has
been changed which accepts host and storage name, with default as None.

Also added test cases to test this support. Changes have been made to
zone manager test code to accommodate the change in signature of some
methods.

Moved get_friendly_zone_name method to utils.py so that it can be used
by both cisco and brocade drivers

Changed cisco driver to accommodate host_name and storage_system
parameters in add/delete connection.

Implements: blueprint brocade-zone-driver-friendly-zone-names

Change-Id: I350493b96901675a1b8910d6104f31c7d677ebda
changes/18/180518/28
Angela Smith 6 years ago
parent
commit
c346612cc7
  1. 113
      cinder/tests/unit/zonemanager/test_driverutils.py
  2. 27
      cinder/tests/unit/zonemanager/test_fc_zone_manager.py
  3. 6
      cinder/tests/unit/zonemanager/test_volume_driver.py
  4. 4
      cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py
  5. 176
      cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py
  6. 1
      cinder/zonemanager/drivers/brocade/fc_zone_constants.py
  7. 57
      cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py
  8. 79
      cinder/zonemanager/drivers/driver_utils.py
  9. 6
      cinder/zonemanager/drivers/fc_zone_driver.py
  10. 90
      cinder/zonemanager/fc_zone_manager.py
  11. 22
      cinder/zonemanager/fczm_constants.py
  12. 24
      cinder/zonemanager/utils.py
  13. 10
      releasenotes/notes/friendly-zone-names-d5e131d356040de0.yaml

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

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

6
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):

4
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

176
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,

1
cinder/zonemanager/drivers/brocade/fc_zone_constants.py

@ -16,7 +16,6 @@
# under the License.
#
"""
Common constants used by Brocade FC Zone Driver.
"""

57
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(

79
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

6
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.

90
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

22
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'

24
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

10
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.
Loading…
Cancel
Save