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:
parent
36e2de2e59
commit
3abd22f7bb
@ -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")
|
||||
|
@ -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'}
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user