Adds support for configuring zoning in a virtual fabric

Through use of HTTP connector, driver is able to set the VFID
context to support any virtual fabric configured in the chassis.

Implements: blueprint brocade-zone-driver-virtualfabrics-support

DocImpact

Change-Id: I52dd2eced18024c8b04107ef6cd797b3a5e19fb3
This commit is contained in:
Angela Smith 2016-02-02 13:00:54 -08:00
parent 36e2de2e59
commit 3abd22f7bb
7 changed files with 349 additions and 7 deletions

View File

@ -121,6 +121,7 @@ class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
password="password",
key="/home/stack/.ssh/id_rsa",
port=22,
vfid="2",
protocol=protocol
)
return client
@ -217,7 +218,7 @@ class FakeClient(object):
class FakeBrcdFCZoneClientCLI(FakeClient):
def __init__(self, ipaddress, username,
password, port, key, protocol):
password, port, key, vfid, protocol):
self.firmware_supported = True
if not GlobalVars._is_normal_test:
raise paramiko.SSHException("Unable to connect to fabric.")
@ -226,7 +227,7 @@ class FakeBrcdFCZoneClientCLI(FakeClient):
class FakeBrcdHttpFCZoneClient(FakeClient):
def __init__(self, ipaddress, username,
password, port, key, protocol):
password, port, key, vfid, protocol):
self.firmware_supported = True
if not GlobalVars._is_normal_test:
raise requests.exception.HTTPError("Unable to connect to fabric")

View File

@ -17,6 +17,7 @@
"""Unit tests for brcd fc zone client http(s)."""
import time
import mock
from mock import patch
from cinder import exception
@ -47,6 +48,7 @@ session = None
active_cfg = 'openstack_cfg'
activate = True
no_activate = False
vf_enable = True
ns_info = ['10:00:00:05:1e:7c:64:96']
nameserver_info = """
<HTML>
@ -253,6 +255,157 @@ didOffset=96
swFWVersion=v7.3.0b_rc1_bld06
swDomain=2
"""
parsed_session_info_vf = """
sessionId=524461483
user=admin
userRole=admin
isAdminRole=Yes
authSource=0
sessionIp=172.26.1.146
valid=yes
adName=
adId=128
adCapable=1
currentAD=AD0
currentADId=0
homeAD=AD0
trueADEnvironment=0
adList=
adIdList=
pfAdmin=0
switchIsMember=0
definedADList=AD0,Physical Fabric
definedADIdList=0,255,
effectiveADList=AD0,Physical Fabric
rc=0
err=
contextType=1
vfEnabled=true
vfSupported=true
HomeVF=128
sessionLFId=2
isContextManageable=1
manageableLFList=2,128,
activeLFList=128,2,
"""
session_info_vf = """
<BODY>
<PRE>
--BEGIN SESSION
sessionId=524461483
user=admin
userRole=admin
isAdminRole=Yes
authSource=0
sessionIp=172.26.1.146
valid=yes
adName=
adId=128
adCapable=1
currentAD=AD0
currentADId=0
homeAD=AD0
trueADEnvironment=0
adList=
adIdList=
pfAdmin=0
switchIsMember=0
definedADList=AD0,Physical Fabric
definedADIdList=0,255,
effectiveADList=AD0,Physical Fabric
rc=0
err=
contextType=1
vfEnabled=true
vfSupported=true
HomeVF=128
sessionLFId=2
isContextManageable=1
manageableLFList=2,128,
activeLFList=128,2,
--END SESSION
</PRE>
</BODY>
"""
session_info_vf_not_changed = """
<BODY>
<PRE>
--BEGIN SESSION
sessionId=524461483
user=admin
userRole=admin
isAdminRole=Yes
authSource=0
sessionIp=172.26.1.146
User-Agent=Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML,
valid=yes
adName=
adId=128
adCapable=1
currentAD=AD0
currentADId=0
homeAD=AD0
trueADEnvironment=0
adList=
adIdList=
pfAdmin=0
switchIsMember=0
definedADList=AD0,Physical Fabric
definedADIdList=0,255,
effectiveADList=AD0,Physical Fabric
rc=0
err=
contextType=1
vfEnabled=true
vfSupported=true
HomeVF=128
sessionLFId=128
isContextManageable=1
manageableLFList=2,128,
activeLFList=128,2,
--END SESSION
</PRE>
</BODY>
"""
session_info_AD = """<HTML>
<HEAD>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">
<TITLE>Webtools Session Info</TITLE>
</HEAD>
<BODY>
<PRE>
--BEGIN SESSION
sessionId=-2096740776
user=
userRole=root
isAdminRole=No
authSource=0
sessionIp=
User-Agent=
valid=no
adName=
adId=0
adCapable=1
currentAD=AD0
currentADId=0
homeAD=AD0
trueADEnvironment=0
adList=
adIdList=
pfAdmin=0
switchIsMember=1
definedADList=AD0,Physical Fabric
definedADIdList=0,255,
effectiveADList=AD0,Physical Fabric
rc=-2
err=Could not obtain session data from store
contextType=0
--END SESSION
</PRE>
</BODY>
</HTML>
"""
zone_info = """<HTML>
<HEAD>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
@ -367,6 +520,70 @@ class TestBrcdHttpFCZoneClient(client.BrcdHTTPFCZoneClient, test.TestCase):
parsed_value,
invalid_keyname)
def test_get_managable_vf_list(self):
manageable_list = ['2', '128']
self.assertEqual(
manageable_list, self.get_managable_vf_list(session_info_vf))
self.assertRaises(exception.BrocadeZoningHttpException,
self.get_managable_vf_list, session_info_AD)
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'is_vf_enabled')
def test_check_change_vf_context_vf_enabled(self, is_vf_enabled_mock):
is_vf_enabled_mock.return_value = (True, session_info_vf)
self.vfid = None
self.assertRaises(
exception.BrocadeZoningHttpException,
self.check_change_vf_context)
self.vfid = "2"
with mock.patch.object(self, 'change_vf_context') \
as change_vf_context_mock:
self.check_change_vf_context()
change_vf_context_mock.assert_called_once_with(
self.vfid, session_info_vf)
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'is_vf_enabled')
def test_check_change_vf_context_vf_disabled(self, is_vf_enabled_mock):
is_vf_enabled_mock.return_value = (False, session_info_AD)
self.vfid = "128"
self.assertRaises(
exception.BrocadeZoningHttpException,
self.check_change_vf_context)
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'get_managable_vf_list')
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'connect')
def test_change_vf_context_valid(self, connect_mock,
get_managable_vf_list_mock):
get_managable_vf_list_mock.return_value = ['2', '128']
connect_mock.return_value = session_info_vf
self.assertIsNone(self.change_vf_context("2", session_info_vf))
data = zone_constant.CHANGE_VF.format(vfid="2")
headers = {zone_constant.AUTH_HEADER: self.auth_header}
connect_mock.assert_called_once_with(
zone_constant.POST_METHOD, zone_constant.SESSION_PAGE,
data, headers)
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'get_managable_vf_list')
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'connect')
def test_change_vf_context_vf_not_changed(self,
connect_mock,
get_managable_vf_list_mock):
get_managable_vf_list_mock.return_value = ['2', '128']
connect_mock.return_value = session_info_vf_not_changed
self.assertRaises(exception.BrocadeZoningHttpException,
self.change_vf_context, "2", session_info_vf)
data = zone_constant.CHANGE_VF.format(vfid="2")
headers = {zone_constant.AUTH_HEADER: self.auth_header}
connect_mock.assert_called_once_with(
zone_constant.POST_METHOD, zone_constant.SESSION_PAGE,
data, headers)
@mock.patch.object(client.BrcdHTTPFCZoneClient, 'get_managable_vf_list')
def test_change_vf_context_vfid_not_managaed(self,
get_managable_vf_list_mock):
get_managable_vf_list_mock.return_value = ['2', '128']
self.assertRaises(exception.BrocadeZoningHttpException,
self.change_vf_context, "12", session_info_vf)
@patch.object(client.BrcdHTTPFCZoneClient, 'connect')
def test_is_supported_firmware(self, connect_mock):
connect_mock.return_value = switch_page_resp
@ -499,6 +716,11 @@ class TestBrcdHttpFCZoneClient(client.BrcdHTTPFCZoneClient, test.TestCase):
connect_mock.return_value = nameserver_info
self.assertEqual(ns_info, self.get_nameserver_info())
@patch.object(client.BrcdHTTPFCZoneClient, 'get_session_info')
def test_is_vf_enabled(self, get_session_info_mock):
get_session_info_mock.return_value = session_info_vf
self.assertEqual((True, parsed_session_info_vf), self.is_vf_enabled())
def test_delete_update_zones_cfgs(self):
cfgs = {'openstack_cfg': 'zone1;zone2'}

View File

@ -48,6 +48,9 @@ brcd_zone_opts = [
cfg.StrOpt('zone_name_prefix',
default='openstack',
help='Overridden zone name prefix.'),
cfg.StrOpt('fc_virtual_fabric_id',
default=None,
help='Virtual Fabric ID.'),
cfg.StrOpt('principal_switch_wwn',
default=None,
deprecated_for_removal=True,

View File

@ -47,6 +47,7 @@ class BrcdFCZoneFactory(object):
fabric_user = fabric.safe_get('fc_fabric_user')
fabric_pwd = fabric.safe_get('fc_fabric_password')
fabric_port = fabric.safe_get('fc_fabric_port')
fc_vfid = fabric.safe_get('fc_virtual_fabric_id')
fabric_ssh_cert_path = fabric.safe_get('fc_fabric_ssh_cert_path')
LOG.debug("Client not found. Creating connection client for"
@ -55,7 +56,8 @@ class BrcdFCZoneFactory(object):
{'ip': fabric_ip,
'connector': sb_connector,
'user': fabric_user,
'port': fabric_port})
'port': fabric_port,
'vf_id': fc_vfid})
if sb_connector.lower() in (fc_zone_constants.HTTP,
fc_zone_constants.HTTPS):
@ -66,6 +68,7 @@ class BrcdFCZoneFactory(object):
username=fabric_user,
password=fabric_pwd,
port=fabric_port,
vfid=fc_vfid,
protocol=sb_connector
)
else:

View File

@ -68,9 +68,10 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
1.1 - Implements performance enhancements
1.2 - Added support for friendly zone name
1.3 - Added HTTP connector support
1.4 - Adds support to zone in Virtual Fabrics
"""
VERSION = "1.3"
VERSION = "1.4"
def __init__(self, **kwargs):
super(BrcdFCZoneDriver, self).__init__(**kwargs)

View File

@ -24,7 +24,7 @@ import six
import time
from cinder import exception
from cinder.i18n import _
from cinder.i18n import _, _LI
import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
@ -34,22 +34,24 @@ LOG = logging.getLogger(__name__)
class BrcdHTTPFCZoneClient(object):
def __init__(self, ipaddress, username,
password, port, protocol):
password, port, vfid, protocol):
"""Initializing the client with the parameters passed.
Creates authentication token and authenticate with switch
to ensure the credentials are correct.
to ensure the credentials are correct and change the VF context.
:param ipaddress: IP Address of the device.
:param username: User id to login.
:param password: User password.
:param port: Device Communication port
:param vfid: Virtual Fabric ID.
:param protocol: Communication Protocol.
"""
self.switch_ip = ipaddress
self.switch_user = username
self.switch_pwd = password
self.protocol = protocol
self.vfid = vfid
self.cfgs = {}
self.zones = {}
self.alias = {}
@ -67,6 +69,7 @@ class BrcdHTTPFCZoneClient(object):
# If authenticated successfully, save the auth status and
# create auth header for future communication with the device.
self.is_auth, self.auth_header = self.authenticate()
self.check_change_vf_context()
def connect(self, requestType, requestURL, payload='', header=None):
"""Connect to the switch using HTTP/HTTPS protocol.
@ -259,6 +262,71 @@ class BrcdHTTPFCZoneClient(object):
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
def get_managable_vf_list(self, session_info):
"""List of VFIDs that can be managed.
:param session_info: Session information from the switch
:returns: manageable VF list
:raises: BrocadeZoningHttpException
"""
try:
# Check the value of manageableLFList NVP,
# throw exception as not supported if the nvp not available
vf_list = self.get_nvp_value(session_info,
zone_constant.MANAGEABLE_VF)
if vf_list:
vf_list = vf_list.split(",") # convert the string to list
except exception.BrocadeZoningHttpException as e:
msg = (_("Error while checking whether "
"VF is available for management %s.") % six.text_type(e))
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
return vf_list[:-1]
def change_vf_context(self, vfid, session_data):
"""Change the VF context in the session.
:param vfid: VFID to which context should be changed.
:param session_data: Session information from the switch
:raises: BrocadeZoningHttpException
"""
try:
managable_vf_list = self.get_managable_vf_list(session_data)
LOG.debug("Manageable VF IDs are %(vflist)s.",
{'vflist': managable_vf_list})
# proceed changing the VF context
# if VF id can be managed if not throw exception
if vfid in managable_vf_list:
headers = {zone_constant.AUTH_HEADER: self.auth_header}
data = zone_constant.CHANGE_VF.format(vfid=vfid)
response = self.connect(zone_constant.POST_METHOD,
zone_constant.SESSION_PAGE,
data,
headers)
parsed_info = self.get_parsed_data(response,
zone_constant.SESSION_BEGIN,
zone_constant.SESSION_END)
session_LF_Id = self.get_nvp_value(parsed_info,
zone_constant.SESSION_LF_ID)
if session_LF_Id == vfid:
LOG.info(_LI("VF context is changed in the session."))
else:
msg = _("Cannot change VF context in the session.")
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
else:
msg = (_("Cannot change VF context, "
"specified VF is not available "
"in the manageable VF list %(vf_list)s.")
% {'vf_list': managable_vf_list})
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
except exception.BrocadeZoningHttpException as e:
msg = (_("Error while changing VF context %s.") % six.text_type(e))
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
def get_zone_info(self):
"""Parse all the zone information and store it in the dictionary."""
@ -568,6 +636,23 @@ class BrcdHTTPFCZoneClient(object):
raise exception.BrocadeZoningHttpException(reason=msg)
return zones, cfgs, active_cfg
def is_vf_enabled(self):
"""To check whether VF is enabled or not.
:returns: boolean to indicate VF enabled and session information
"""
session_info = self.get_session_info()
parsed_data = self.get_parsed_data(session_info,
zone_constant.SESSION_BEGIN,
zone_constant.SESSION_END)
try:
is_vf_enabled = bool(self.get_nvp_value(
parsed_data, zone_constant.VF_ENABLED))
except exception.BrocadeZoningHttpException:
is_vf_enabled = False
parsed_data = None
return is_vf_enabled, parsed_data
def get_nameserver_info(self):
"""Get name server data from fabric.
@ -729,6 +814,26 @@ class BrcdHTTPFCZoneClient(object):
zone_constant.ZONE_ERROR_MSG)
return errorCode, errorMessage
def check_change_vf_context(self):
"""Check whether VF related configurations is valid and proceed."""
vf_enabled, session_data = self.is_vf_enabled()
# VF enabled will be false if vf is disable or not supported
LOG.debug("VF enabled on switch: %(vfenabled)s.",
{'vfenabled': vf_enabled})
# Change the VF context in the session
if vf_enabled:
if self.vfid is None:
msg = _("No VF ID is defined in the configuration file.")
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
elif self.vfid != 128:
self.change_vf_context(self.vfid, session_data)
else:
if self.vfid is not None:
msg = _("VF is not enabled.")
LOG.error(msg)
raise exception.BrocadeZoningHttpException(reason=msg)
def cleanup(self):
"""Close session."""
self.session.close()

View File

@ -0,0 +1,7 @@
---
features:
- Support for configuring Fibre Channel zoning on
Brocade switches through Cinder Fibre Channel Zone
Manager and Brocade Fibre Channel zone plugin.
To zone in a Virtual Fabric, set the configuration
option 'fc_virtual_fabric_id' for the fabric.