From 3abd22f7bbc1b00c01de7b8b53fd19c453f822a6 Mon Sep 17 00:00:00 2001 From: Angela Smith Date: Tue, 2 Feb 2016 13:00:54 -0800 Subject: [PATCH] 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 --- .../zonemanager/test_brcd_fc_zone_driver.py | 5 +- .../test_brcd_http_fc_zone_client.py | 222 ++++++++++++++++++ .../drivers/brocade/brcd_fabric_opts.py | 3 + .../brocade/brcd_fc_zone_connector_factory.py | 5 +- .../drivers/brocade/brcd_fc_zone_driver.py | 3 +- .../brocade/brcd_http_fc_zone_client.py | 111 ++++++++- ...tual_fabrics_support-d2d0b95b19457c1d.yaml | 7 + 7 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/brocade_virtual_fabrics_support-d2d0b95b19457c1d.yaml diff --git a/cinder/tests/unit/zonemanager/test_brcd_fc_zone_driver.py b/cinder/tests/unit/zonemanager/test_brcd_fc_zone_driver.py index e42543dc0..385e823b0 100644 --- a/cinder/tests/unit/zonemanager/test_brcd_fc_zone_driver.py +++ b/cinder/tests/unit/zonemanager/test_brcd_fc_zone_driver.py @@ -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") diff --git a/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py b/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py index 739e377b5..627534d8e 100644 --- a/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py +++ b/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py @@ -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 = """ @@ -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 = """ + +
+--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
+
+ +""" +session_info_vf_not_changed = """ + +
+--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
+
+ +""" +session_info_AD = """ + + + +Webtools Session Info + + +
+--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
+
+ + +""" zone_info = """ @@ -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'} diff --git a/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py b/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py index 7cb11f73b..b5b263436 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py +++ b/cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py @@ -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, diff --git a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py index 0a4234d89..a6e7a8a66 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py +++ b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py @@ -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: diff --git a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py index 21cfbe962..b927a6bb7 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py +++ b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py @@ -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) diff --git a/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py b/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py index 7c4398c04..bf3afc411 100644 --- a/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py +++ b/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py @@ -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() diff --git a/releasenotes/notes/brocade_virtual_fabrics_support-d2d0b95b19457c1d.yaml b/releasenotes/notes/brocade_virtual_fabrics_support-d2d0b95b19457c1d.yaml new file mode 100644 index 000000000..bab9bc7df --- /dev/null +++ b/releasenotes/notes/brocade_virtual_fabrics_support-d2d0b95b19457c1d.yaml @@ -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. -- 2.45.2