Set ssl verify to False for HTTPS.
Marked 'principal_switch_wwn' parameter from the config options as
deprecated.
DocImpact
Implements: blueprint brocade-zone-driver-virtualfabrics-support
Change-Id: I0b40b520580eaa6821c0af29abc6d2497d884ad2
class BrocadeZoningCliException(CinderException):
- message = _("Fibre Channel Zoning CLI error: %(reason)s")
+ message = _("Brocade Fibre Channel Zoning CLI error: %(reason)s")
+
+
+class BrocadeZoningHttpException(CinderException):
+ message = _("Brocade Fibre Channel Zoning HTTP error: %(reason)s")
class CiscoZoningCliException(CinderException):
- message = _("Fibre Channel Zoning CLI error: %(reason)s")
+ message = _("Cisco Fibre Channel Zoning CLI error: %(reason)s")
class NetAppDriverException(VolumeDriverException):
# (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
self.assertEqual(parsed_switch_port_wwns, return_wwn_list)
self.assertRaises(exception.InvalidParameterValue,
self._parse_ns_output, invalid_switch_data)
-
- def test_get_formatted_wwn(self):
- wwn_list = ['10008c7cff523b01']
- return_wwn_list = []
- expected_wwn_list = ['10:00:8c:7c:ff:52:3b:01']
- return_wwn_list.append(self.get_formatted_wwn(wwn_list[0]))
- self.assertEqual(expected_wwn_list, return_wwn_list)
# (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
from cinder import exception
from cinder import test
-from cinder.zonemanager.drivers.brocade \
- import brcd_fc_zone_client_cli as client_cli
-import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+from cinder.zonemanager.drivers.brocade import (brcd_fc_zone_client_cli
+ as client_cli)
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
nsshow = '20:1a:00:05:1e:e8:e3:29'
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
def test_get_active_zone_set(self, get_switch_info_mock):
- cmd_list = [ZoneConstant.GET_ACTIVE_ZONE_CFG]
+ cmd_list = [zone_constant.GET_ACTIVE_ZONE_CFG]
get_switch_info_mock.return_value = cfgactvshow
active_zoneset_returned = self.get_active_zone_set()
get_switch_info_mock.assert_called_once_with(cmd_list)
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
def test_get_nameserver_info(self, get_switch_info_mock):
- ns_info_list = []
ns_info_list_expected = ['20:1a:00:05:1e:e8:e3:29']
get_switch_info_mock.return_value = (switch_data)
ns_info_list = self.get_nameserver_info()
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_ssh_execute')
def test__cfg_save(self, ssh_execute_mock):
- cmd_list = [ZoneConstant.CFG_SAVE]
+ cmd_list = [zone_constant.CFG_SAVE]
self._cfg_save()
ssh_execute_mock.assert_called_once_with(cmd_list, True, 1)
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
def test__cfg_trans_abort(self, apply_zone_change_mock):
- cmd_list = [ZoneConstant.CFG_ZONE_TRANS_ABORT]
+ cmd_list = [zone_constant.CFG_ZONE_TRANS_ABORT]
with mock.patch.object(self, '_is_trans_abortable') \
as is_trans_abortable_mock:
is_trans_abortable_mock.return_value = True
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
def test__is_trans_abortable_true(self, run_ssh_mock):
- cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
- run_ssh_mock.return_value = (Stream(ZoneConstant.TRANS_ABORTABLE),
+ cmd_list = [zone_constant.CFG_SHOW_TRANS]
+ run_ssh_mock.return_value = (Stream(zone_constant.TRANS_ABORTABLE),
None)
data = self._is_trans_abortable()
self.assertTrue(data)
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
def test__is_trans_abortable_false(self, run_ssh_mock):
- cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
+ cmd_list = [zone_constant.CFG_SHOW_TRANS]
cfgtransshow = 'There is no outstanding zoning transaction'
run_ssh_mock.return_value = (Stream(cfgtransshow), None)
data = self._is_trans_abortable()
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
def test_apply_zone_change(self, run_ssh_mock):
- cmd_list = [ZoneConstant.CFG_SAVE]
+ cmd_list = [zone_constant.CFG_SAVE]
run_ssh_mock.return_value = (None, None)
self.apply_zone_change(cmd_list)
run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
@mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
def test__get_switch_info(self, run_ssh_mock):
- cmd_list = [ZoneConstant.NS_SHOW]
+ cmd_list = [zone_constant.NS_SHOW]
nsshow_list = [nsshow]
run_ssh_mock.return_value = (Stream(nsshow), Stream())
switch_data = self._get_switch_info(cmd_list)
def test__parse_ns_output(self):
invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
- return_wwn_list = []
expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29']
return_wwn_list = self._parse_ns_output(switch_data)
self.assertEqual(expected_wwn_list, return_wwn_list)
# (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
from oslo_config import cfg
from oslo_utils import importutils
import paramiko
+import requests
from cinder import exception
from cinder import test
configuration.fc_fabric_names = 'BRCD_FAB_1'
configuration.fc_fabric_address_BRCD_FAB_1 = '10.24.48.213'
- if (is_normal):
+ configuration.fc_southbound_connector = 'CLI'
+ if is_normal:
configuration.fc_fabric_user_BRCD_FAB_1 = 'admin'
else:
configuration.fc_fabric_user_BRCD_FAB_1 = 'invaliduser'
configuration.fc_fabric_password_BRCD_FAB_1 = 'password'
- if (mode == 1):
+ if mode == 1:
configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
- elif (mode == 2):
+ elif mode == 2:
configuration.zoning_policy_BRCD_FAB_1 = 'initiator'
else:
configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
def fake__get_active_zone_set(self, brcd_sb_connector, fabric_ip):
return GlobalVars._active_cfg
+ def get_client(self, protocol='HTTPS'):
+ conn = ('cinder.tests.unit.zonemanager.test_brcd_fc_zone_driver.' +
+ ('FakeBrcdFCZoneClientCLI' if protocol == "CLI"
+ else 'FakeBrcdHttpFCZoneClient'))
+ client = importutils.import_object(
+ conn,
+ ipaddress="10.24.48.213",
+ username="admin",
+ password="password",
+ key="/home/stack/.ssh/id_rsa",
+ port=22,
+ protocol=protocol
+ )
+ return client
+
def fake_get_san_context(self, target_wwn_list):
fabric_map = {}
return fabric_map
- @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
- def test_add_connection(self, get_active_zs_mock):
+ @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+ def test_add_connection(self, get_southbound_client_mock):
"""Normal flow for i-t mode."""
GlobalVars._is_normal_test = True
GlobalVars._zone_state = []
- get_active_zs_mock.return_value = _active_cfg_before_add
+ GlobalVars._active_cfg = _active_cfg_before_add
+ get_southbound_client_mock.return_value = self.get_client("HTTPS")
self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
self.assertTrue(_zone_name in GlobalVars._zone_state)
- @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
- def test_delete_connection(self, get_active_zs_mock):
+ @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+ def test_delete_connection(self, get_southbound_client_mock):
GlobalVars._is_normal_test = True
- get_active_zs_mock.return_value = _active_cfg_before_delete
+ get_southbound_client_mock.return_value = self.get_client("CLI")
+ GlobalVars._active_cfg = _active_cfg_before_delete
self.driver.delete_connection(
'BRCD_FAB_1', _initiator_target_map)
self.assertFalse(_zone_name in GlobalVars._zone_state)
- @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
- def test_add_connection_for_initiator_mode(self, get_active_zs_mock):
+ @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+ def test_add_connection_for_initiator_mode(self, get_southbound_client_mk):
"""Normal flow for i mode."""
GlobalVars._is_normal_test = True
- get_active_zs_mock.return_value = _active_cfg_before_add
+ get_southbound_client_mk.return_value = self.get_client("CLI")
+ GlobalVars._active_cfg = _active_cfg_before_add
self.setup_driver(self.setup_config(True, 2))
self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
self.assertTrue(_zone_name in GlobalVars._zone_state)
- @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
- def test_delete_connection_for_initiator_mode(self, get_active_zs_mock):
+ @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+ def test_delete_connection_for_initiator_mode(self,
+ get_southbound_client_mk):
GlobalVars._is_normal_test = True
- get_active_zs_mock.return_value = _active_cfg_before_delete
+ get_southbound_client_mk.return_value = self.get_client("HTTPS")
+ GlobalVars._active_cfg = _active_cfg_before_delete
self.setup_driver(self.setup_config(True, 2))
self.driver.delete_connection(
'BRCD_FAB_1', _initiator_target_map)
_initiator_target_map)
-class FakeBrcdFCZoneClientCLI(object):
- def __init__(self, ipaddress, username, password, port):
- self.firmware_supported = True
- if not GlobalVars._is_normal_test:
- raise paramiko.SSHException("Unable to connect to fabric")
-
+class FakeClient(object):
def get_active_zone_set(self):
return GlobalVars._active_cfg
pass
+class FakeBrcdFCZoneClientCLI(FakeClient):
+ def __init__(self, ipaddress, username,
+ password, port, key, protocol):
+ self.firmware_supported = True
+ if not GlobalVars._is_normal_test:
+ raise paramiko.SSHException("Unable to connect to fabric.")
+
+
+class FakeBrcdHttpFCZoneClient(FakeClient):
+
+ def __init__(self, ipaddress, username,
+ password, port, key, protocol):
+ self.firmware_supported = True
+ if not GlobalVars._is_normal_test:
+ raise requests.exception.HTTPError("Unable to connect to fabric")
+
+
class FakeBrcdFCSanLookupService(object):
+
def get_device_mapping_from_network(self,
initiator_wwn_list,
target_wwn_list):
initiators = []
targets = []
for i in initiator_wwn_list:
- if (i in _initiator_ns_map[_fabric_wwn]):
+ if i in _initiator_ns_map[_fabric_wwn]:
initiators.append(i)
for t in target_wwn_list:
- if (t in _target_ns_map[_fabric_wwn]):
+ if t in _target_ns_map[_fabric_wwn]:
targets.append(t)
device_map[_fabric_wwn] = {
'initiator_port_wwn_list': initiators,
--- /dev/null
+# (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 brcd fc zone client http(s)."""
+from mock import patch
+
+from cinder import exception
+from cinder import test
+from cinder.zonemanager.drivers.brocade import (brcd_http_fc_zone_client
+ as client)
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
+
+
+cfgs = {'openstack_cfg': 'zone1;zone2'}
+cfgs_to_delete = {
+ 'openstack_cfg': 'zone1;zone2;openstack50060b0000c26604201900051ee8e329'}
+zones = {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'}
+
+zones_to_delete = {
+ 'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+ 'openstack50060b0000c26604201900051ee8e329':
+ '50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29'}
+
+alias = {}
+qlps = {}
+ifas = {}
+parsed_raw_zoneinfo = ""
+random_no = ''
+session = None
+active_cfg = 'openstack_cfg'
+activate = True
+no_activate = False
+ns_info = ['10:00:00:05:1e:7c:64:96']
+nameserver_info = """
+<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>NSInfo Page</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN NS INFO
+
+2;8;020800;N ;10:00:00:05:1e:7c:64:96;20:00:00:05:1e:7c:64:96;[89]""" \
+"""Brocade-825 | 3.0.4.09 | DCM-X3650-94 | Microsoft Windows Server 2003 R2"""\
+ """| Service Pack 2";FCP ; 3;20:08:00:05:1e:89:54:a0;"""\
+ """0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0;000000;port8"""\
+ """
+--END NS INFO
+
+</PRE>
+</BODY>
+</HTML>
+"""
+mocked_zone_string = 'zonecfginfo=\ 1openstack_cfg zone1;zone2 '\
+ '\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
+ '\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
+ '\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
+ '\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+ '\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+ '\aopenstack_cfg null \ 5&saveonly=false'
+mocked_zone_string_no_activate = 'zonecfginfo=\ 1openstack_cfg zone1;zone2 '\
+ '\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
+ '\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
+ '\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
+ '\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+ '\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c \ 5&saveonly=true'
+zone_string_to_post = "zonecfginfo=\ 1openstack_cfg "\
+ "openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
+ "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+ "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+ "\ 2openstack50060b0000c26604201900051ee8e329 "\
+ "50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 "\
+ "\aopenstack_cfg null \ 5&saveonly=false"
+zone_string_to_post_no_activate = "zonecfginfo=\ 1openstack_cfg "\
+ "openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
+ "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+ "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+ "\ 2openstack50060b0000c26604201900051ee8e329 "\
+ "50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 \ 5&saveonly=true"
+zone_string_to_post_invalid_request = "zonecfginfo=\ 1openstack_cfg "\
+ "openstack50060b0000c26604201900051ee8e32900000000000000000000000000;"\
+ "zone1;zone2 \ 2openstack50060b0000c26604201900051ee8e329000000000000000000000"\
+ "00000 50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 "\
+ "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+ "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 \ 5&saveonly=true"
+zone_string_del_to_post = "zonecfginfo=\ 1openstack_cfg zone1;zone2"\
+ " \ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+ "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+ "\aopenstack_cfg null \ 5&saveonly=false"
+zone_string_del_to_post_no_active = "zonecfginfo=\ 1openstack_cfg zone1;zone2"\
+ " \ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+ "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 \ 5&saveonly=true"
+zone_post_page = """
+<BODY>
+<PRE>
+--BEGIN ZONE_TXN_INFO
+txnId=34666
+adId=0
+user=admin
+roleUser=admin
+openTxnOwner=
+openTxnId=0
+openTxnAbortable=0
+txnStarttime=1421916354
+txnEndtime=1421916355
+currStateInt=4
+prevStateInt=3
+actionInt=5
+currState=done
+prevState=progress
+action=error
+sessionId=5892021
+selfAborted=false
+status=done
+errorCode=-1
+errorMessage=Name too long
+--END ZONE_TXN_INFO
+</PRE>
+</BODY>"""
+zone_post_page_no_error = """
+<BODY>
+<PRE>
+--BEGIN ZONE_TXN_INFO
+txnId=34666
+adId=0
+user=admin
+roleUser=admin
+openTxnOwner=
+openTxnId=0
+openTxnAbortable=0
+txnStarttime=1421916354
+txnEndtime=1421916355
+currStateInt=4
+prevStateInt=3
+actionInt=5
+currState=done
+prevState=progress
+action=error
+sessionId=5892021
+selfAborted=false
+status=done
+errorCode=0
+errorMessage=
+--END ZONE_TXN_INFO
+</PRE>
+</BODY>"""
+secinfo_resp = """
+<BODY>
+<PRE>
+--BEGIN SECINFO
+SECURITY = OFF
+RANDOM = 6281590
+DefaultPasswdBitmap = 0
+primaryFCS = no
+switchType = 66
+resource = 10.24.48.210
+REALM = FC Switch Administration
+AUTHMETHOD = Custom_Basic
+hasUpfrontLogin=yes
+AUTHVERSION = 1
+vfEnabled=false
+vfSupported=true
+--END SECINFO
+</PRE>
+</BODY>
+"""
+authenticate_resp = """<HTML>
+<PRE>
+--BEGIN AUTHENTICATE
+authenticated = yes
+username=admin
+userrole=admin
+adCapable=1
+currentAD=AD0
+trueADEnvironment=0
+adId=0
+adList=ALL
+contextType=0
+--END AUTHENTICATE
+</PRE>
+</BODY>
+"""
+un_authenticate_resp = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>Authentication</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN AUTHENTICATE
+authenticated = no
+errCode = -3
+authType = Custom_Basic
+realm = FC Switch Administration
+--END AUTHENTICATE
+</PRE>
+</BODY>
+</HTML>"""
+switch_page_resp = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN SWITCH INFORMATION
+didOffset=96
+swFWVersion=v7.3.0b_rc1_bld06
+swDomain=2
+--END SWITCH INFORMATION
+</PRE>
+</BODY>
+</HTML>
+"""
+switch_page_invalid_firm = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN SWITCH INFORMATION
+didOffset=96
+swFWVersion=v6.1.1
+swDomain=2
+--END SWITCH INFORMATION
+</PRE>
+</BODY>
+</HTML>
+"""
+parsed_value = """
+didOffset=96
+swFWVersion=v7.3.0b_rc1_bld06
+swDomain=2
+"""
+zone_info = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>Zone Configuration Information</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN ZONE CHANGE
+LastZoneChangeTime=1421926251
+--END ZONE CHANGE
+isZoneTxnSupported=true
+ZoneLicense=true
+QuickLoopLicense=true
+DefZoneStatus=noaccess
+McDataDefaultZone=false
+McDataSafeZone=false
+AvailableZoneSize=1043890
+--BEGIN ZONE INFO
+\ 1openstack_cfg zone1;zone2 """\
+"""\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 """\
+ """\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 """\
+ """\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 """\
+ """\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
+ """\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
+ """\aopenstack_cfg null \ 51045274"""\
+ """--END ZONE INFO
+</PRE>
+</BODY>
+</HTML>
+
+"""
+
+active_zone_set = {
+ 'zones':
+ {'zone1':
+ ['20:01:00:05:33:0e:96:15', '20:00:00:05:33:0e:93:11'],
+ 'zone2':
+ ['20:01:00:05:33:0e:96:14', '20:00:00:05:33:0e:93:11']},
+ 'active_zone_config': 'openstack_cfg'}
+updated_zones = {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+ 'test_updated_zone':
+ '20:01:00:05:33:0e:96:10;20:00:00:05:33:0e:93:11'}
+updated_cfgs = {'openstack_cfg': 'test_updated_zone;zone1;zone2'}
+valid_zone_name = "openstack50060b0000c26604201900051ee8e329"
+
+
+class TestBrcdHttpFCZoneClient(client.BrcdHTTPFCZoneClient, test.TestCase):
+
+ def setUp(self):
+ self.auth_header = "YWRtaW46cGFzc3dvcmQ6NDM4ODEyNTIw"
+ self.switch_user = "admin"
+ self.switch_pwd = "password"
+ self.protocol = "HTTPS"
+ self.conn = None
+ self.alias = {}
+ self.qlps = {}
+ self.ifas = {}
+ self.parsed_raw_zoneinfo = ""
+ self.random_no = ''
+ self.session = None
+ super(TestBrcdHttpFCZoneClient, self).setUp()
+
+ # override some of the functions
+ def __init__(self, *args, **kwargs):
+ test.TestCase.__init__(self, *args, **kwargs)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_create_auth_token(self, connect_mock):
+ connect_mock.return_value = secinfo_resp
+ self.assertEqual("Custom_Basic YWRtaW46cGFzc3dvcmQ6NjI4MTU5MA==",
+ self.create_auth_token())
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_authenticate(self, connect_mock):
+ connect_mock.return_value = authenticate_resp
+ self.assertEqual(
+ (True, "Custom_Basic YWRtaW46eHh4Og=="), self.authenticate())
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_authenticate_failed(self, connect_mock):
+ connect_mock.return_value = un_authenticate_resp
+ self.assertRaises(
+ exception.BrocadeZoningHttpException, self.authenticate)
+
+ def test_get_parsed_data(self):
+ valid_delimiter1 = zone_constant.SWITCHINFO_BEGIN
+ valid_delimiter2 = zone_constant.SWITCHINFO_END
+ invalid_delimiter = "--END SWITCH INFORMATION1"
+ self.assertEqual(parsed_value, self.get_parsed_data(
+ switch_page_resp, valid_delimiter1, valid_delimiter2))
+ self.assertRaises(exception.BrocadeZoningHttpException,
+ self.get_parsed_data,
+ switch_page_resp,
+ valid_delimiter1,
+ invalid_delimiter)
+ self.assertRaises(exception.BrocadeZoningHttpException,
+ self.get_parsed_data,
+ switch_page_resp,
+ invalid_delimiter,
+ valid_delimiter2)
+
+ def test_get_nvp_value(self):
+ valid_keyname = zone_constant.FIRMWARE_VERSION
+ invalid_keyname = "swFWVersion1"
+ self.assertEqual(
+ "v7.3.0b_rc1_bld06", self.get_nvp_value(parsed_value,
+ valid_keyname))
+ self.assertRaises(exception.BrocadeZoningHttpException,
+ self.get_nvp_value,
+ parsed_value,
+ invalid_keyname)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_is_supported_firmware(self, connect_mock):
+ connect_mock.return_value = switch_page_resp
+ self.assertTrue(self.is_supported_firmware())
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_is_supported_firmware_invalid(self, connect_mock):
+ connect_mock.return_value = switch_page_invalid_firm
+ self.assertFalse(self.is_supported_firmware())
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_get_active_zone_set(self, connect_mock):
+ connect_mock.return_value = zone_info
+ returned_zone_map = self.get_active_zone_set()
+ self.assertDictMatch(active_zone_set, returned_zone_map)
+
+ def test_form_zone_string(self):
+ new_alias = {
+ 'alia1': '10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12'}
+ new_qlps = {'qlp': '10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c'}
+ new_ifas = {'fa1': '20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c'}
+ self.assertEqual(mocked_zone_string, self.form_zone_string(
+ cfgs, active_cfg, zones, new_alias, new_qlps, new_ifas, True))
+ self.assertEqual(mocked_zone_string_no_activate, self.form_zone_string(
+ cfgs, active_cfg, zones, new_alias, new_qlps, new_ifas, False))
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_add_zones_activate(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("0", "")
+ self.cfgs = cfgs.copy()
+ self.zones = zones.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ add_zones_info = {valid_zone_name:
+ ['50:06:0b:00:00:c2:66:04',
+ '20:19:00:05:1e:e8:e3:29']
+ }
+ self.add_zones(add_zones_info, True)
+ post_zone_data_mock.assert_called_once_with(zone_string_to_post)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_add_zones_invalid_zone_name(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("-1", "Name Too Long")
+ self.cfgs = cfgs.copy()
+ self.zones = zones.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ invalid_zone_name = valid_zone_name + "00000000000000000000000000"
+ add_zones_info = {invalid_zone_name:
+ ['50:06:0b:00:00:c2:66:04',
+ '20:19:00:05:1e:e8:e3:29']
+ }
+ self.assertRaises(
+ exception.BrocadeZoningHttpException,
+ self.add_zones, add_zones_info, False)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_add_zones_no_activate(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("0", "")
+ self.cfgs = cfgs.copy()
+ self.zones = zones.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ add_zones_info = {valid_zone_name:
+ ['50:06:0b:00:00:c2:66:04',
+ '20:19:00:05:1e:e8:e3:29']
+ }
+ self.add_zones(add_zones_info, False)
+ post_zone_data_mock.assert_called_once_with(
+ zone_string_to_post_no_activate)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_delete_zones_activate(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("0", "")
+ self.cfgs = cfgs_to_delete.copy()
+ self.zones = zones_to_delete.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ delete_zones_info = valid_zone_name
+
+ self.delete_zones(delete_zones_info, True)
+ post_zone_data_mock.assert_called_once_with(zone_string_del_to_post)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_delete_zones_no_activate(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("0", "")
+ self.cfgs = cfgs_to_delete.copy()
+ self.zones = zones_to_delete.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ delete_zones_info = valid_zone_name
+ self.delete_zones(delete_zones_info, False)
+ post_zone_data_mock.assert_called_once_with(
+ zone_string_del_to_post_no_active)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+ def test_delete_zones_invalid_zone_name(self, post_zone_data_mock):
+ post_zone_data_mock.return_value = ("0", "")
+ self.cfgs = cfgs_to_delete.copy()
+ self.zones = zones_to_delete.copy()
+ self.alias = alias.copy()
+ self.qlps = qlps.copy()
+ self.ifas = ifas.copy()
+ self.active_cfg = active_cfg
+ delete_zones_info = 'openstack50060b0000c26604201900051ee8e32'
+ self.assertRaises(exception.BrocadeZoningHttpException,
+ self.delete_zones, delete_zones_info, False)
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_post_zone_data(self, connect_mock):
+ connect_mock.return_value = zone_post_page
+ self.assertEqual(
+ ("-1", "Name too long"), self.post_zone_data(zone_string_to_post))
+ connect_mock.return_value = zone_post_page_no_error
+ self.assertEqual(("0", ""), self.post_zone_data(zone_string_to_post))
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_get_nameserver_info(self, connect_mock):
+ connect_mock.return_value = nameserver_info
+ self.assertEqual(ns_info, self.get_nameserver_info())
+
+ def test_delete_update_zones_cfgs(self):
+
+ cfgs = {'openstack_cfg': 'zone1;zone2'}
+ zones = {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'}
+ delete_zones_info = valid_zone_name
+ self.assertEqual(
+ (zones, cfgs, active_cfg),
+ self.delete_update_zones_cfgs(
+ cfgs_to_delete.copy(),
+ zones_to_delete.copy(),
+ delete_zones_info,
+ active_cfg))
+
+ cfgs = {'openstack_cfg': 'zone2'}
+ zones = {'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'}
+ delete_zones_info = valid_zone_name + ";zone1"
+ self.assertEqual(
+ (zones, cfgs, active_cfg),
+ self.delete_update_zones_cfgs(
+ cfgs_to_delete.copy(),
+ zones_to_delete.copy(),
+ delete_zones_info,
+ active_cfg))
+
+ def test_add_update_zones_cfgs(self):
+ add_zones_info = {valid_zone_name:
+ ['50:06:0b:00:00:c2:66:04',
+ '20:19:00:05:1e:e8:e3:29']
+ }
+ updated_cfgs = {
+ 'openstack_cfg':
+ valid_zone_name + ';zone1;zone2'}
+ updated_zones = {
+ 'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+ valid_zone_name:
+ '50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29'}
+ self.assertEqual((updated_zones, updated_cfgs, active_cfg),
+ self.add_update_zones_cfgs(
+ cfgs.copy(),
+ zones.copy(),
+ add_zones_info,
+ active_cfg,
+ "openstack_cfg"))
+
+ add_zones_info = {valid_zone_name:
+ ['50:06:0b:00:00:c2:66:04',
+ '20:19:00:05:1e:e8:e3:29'],
+ 'test4':
+ ['20:06:0b:00:00:b2:66:07',
+ '20:10:00:05:1e:b8:c3:19']
+ }
+ updated_cfgs = {
+ 'openstack_cfg':
+ 'test4;openstack50060b0000c26604201900051ee8e329;zone1;zone2'}
+ updated_zones = {
+ 'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+ valid_zone_name:
+ '50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29',
+ 'test4': '20:06:0b:00:00:b2:66:07;20:10:00:05:1e:b8:c3:19'}
+ self.assertEqual(
+ (updated_zones, updated_cfgs, active_cfg),
+ self.add_update_zones_cfgs(
+ cfgs.copy(), zones.copy(), add_zones_info,
+ active_cfg, "openstack_cfg"))
+
+ @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+ def test_get_zone_info(self, connect_mock):
+ connect_mock.return_value = zone_info
+ self.get_zone_info()
+ self.assertEqual({'openstack_cfg': 'zone1;zone2'}, self.cfgs)
+ self.assertEqual(
+ {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+ 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'},
+ self.zones)
+ self.assertEqual('openstack_cfg', self.active_cfg)
+ self.assertEqual(
+ {'alia1': '10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12'},
+ self.alias)
+ self.assertEqual(
+ {'fa1': '20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c'},
+ self.ifas)
+ self.assertEqual(
+ {'qlp': '10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c'},
+ self.qlps)
# (c) Copyright 2013 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
# under the License.
#
-
"""Unit tests for fc san lookup service."""
from cinder import exception
# (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
from cinder.volume import configuration
brcd_zone_opts = [
+ cfg.StrOpt('fc_southbound_protocol',
+ default='HTTP',
+ choices=('SSH', 'HTTP', 'HTTPS'),
+ help='South bound connector for the fabric.'),
cfg.StrOpt('fc_fabric_address',
default='',
- help='Management IP of fabric'),
+ help='Management IP of fabric.'),
cfg.StrOpt('fc_fabric_user',
default='',
- help='Fabric user ID'),
+ help='Fabric user ID.'),
cfg.StrOpt('fc_fabric_password',
default='',
- help='Password for user',
+ help='Password for user.',
secret=True),
cfg.PortOpt('fc_fabric_port',
default=22,
help='Connecting port'),
+ cfg.StrOpt('fc_fabric_ssh_cert_path',
+ default='',
+ help='Local SSH certificate Path.'),
cfg.StrOpt('zoning_policy',
default='initiator-target',
- help='overridden zoning policy'),
+ help='Overridden zoning policy.'),
cfg.BoolOpt('zone_activate',
default=True,
- help='overridden zoning activation state'),
+ help='Overridden zoning activation state.'),
cfg.StrOpt('zone_name_prefix',
default='openstack',
- help='overridden zone name prefix'),
+ help='Overridden zone name prefix.'),
cfg.StrOpt('principal_switch_wwn',
- help='Principal switch WWN of the fabric'),
+ default=None,
+ deprecated_for_removal=True,
+ help='Principal switch WWN of the fabric. This option is not '
+ 'used anymore.')
]
CONF = cfg.CONF
LOG.debug("Loaded FC fabric config %(fabricname)s",
{'fabricname': fabric_name})
fabric_configs[fabric_name] = config
-
return fabric_configs
from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as fabric_opts
import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
from cinder.zonemanager import fc_san_lookup_service as fc_service
+from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
LOG.debug("FC Fabric List: %s", fabrics)
if fabrics:
for t in target_wwn_list:
- formatted_target_list.append(self.get_formatted_wwn(t))
+ formatted_target_list.append(fczm_utils.get_formatted_wwn(t))
for i in initiator_wwn_list:
- formatted_initiator_list.append(self.
+ formatted_initiator_list.append(fczm_utils.
get_formatted_wwn(i))
for fabric_name in fabrics:
LOG.error(msg)
raise exception.InvalidParameterValue(err=msg)
return nsinfo_list
-
- def get_formatted_wwn(self, wwn_str):
- """Utility API that formats WWN to insert ':'."""
- if (len(wwn_str) != 16):
- return wwn_str.lower()
- else:
- return (':'.join([wwn_str[i:i + 2]
- for i in range(0, len(wwn_str), 2)])).lower()
# (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
from cinder.i18n import _, _LE
from cinder import ssh_utils
from cinder import utils
-import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
LOG = logging.getLogger(__name__)
switch_port = '22'
switch_user = 'admin'
switch_pwd = 'none'
+ switch_key = 'none'
patrn = re.compile('[;\s]+')
- def __init__(self, ipaddress, username, password, port):
- """initializing the client."""
+ def __init__(self, ipaddress, username,
+ password, port, key):
+ """Initializing the client."""
self.switch_ip = ipaddress
self.switch_port = port
self.switch_user = username
self.switch_pwd = password
+ self.switch_key = key
self.sshpool = None
def get_active_zone_set(self):
zone_set_name = None
try:
switch_data = self._get_switch_info(
- [ZoneConstant.GET_ACTIVE_ZONE_CFG])
+ [zone_constant.GET_ACTIVE_ZONE_CFG])
except exception.BrocadeZoningCliException:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Failed getting active zone set "
line_split = [x.replace(
' ',
'') for x in line_split]
- if ZoneConstant.CFG_ZONESET in line_split:
+ if zone_constant.CFG_ZONESET in line_split:
zone_set_name = line_split[1]
continue
if line_split[1]:
zone_member = line_split[2]
zone_member_list = zone.get(zone_name)
zone_member_list.append(zone_member)
- zone_set[ZoneConstant.CFG_ZONES] = zone
- zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG] = zone_set_name
+ zone_set[zone_constant.CFG_ZONES] = zone
+ zone_set[zone_constant.ACTIVE_ZONE_CONFIG] = zone_set_name
except Exception:
- # Incase of parsing error here, it should be malformed cli output.
+ # In case of parsing error here, it should be malformed cli output.
msg = _("Malformed zone configuration: (switch=%(switch)s "
"zone_config=%(zone_config)s)."
) % {'switch': self.switch_ip,
if not active_zone_set:
active_zone_set = self.get_active_zone_set()
LOG.debug("Active zone set: %s", active_zone_set)
- zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+ zone_list = active_zone_set[zone_constant.CFG_ZONES]
LOG.debug("zone list: %s", zone_list)
for zone in zones.keys():
# If zone exists, its an update. Delete & insert
# Get active zone set from device, as some of the zones
# could be deleted.
active_zone_set = self.get_active_zone_set()
- cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
+ cfg_name = active_zone_set[zone_constant.ACTIVE_ZONE_CONFIG]
cmd = None
if not cfg_name:
- cfg_name = ZoneConstant.OPENSTACK_CFG_NAME
+ cfg_name = zone_constant.OPENSTACK_CFG_NAME
cmd = 'cfgcreate "%(zoneset)s", "%(zones)s"' \
% {'zoneset': cfg_name, 'zones': zone_with_sep}
else:
def activate_zoneset(self, cfgname):
"""Method to Activate the zone config. Param cfgname - ZonesetName."""
- cmd_list = [ZoneConstant.ACTIVATE_ZONESET, cfgname]
+ cmd_list = [zone_constant.ACTIVATE_ZONESET, cfgname]
return self._ssh_execute(cmd_list, True, 1)
def deactivate_zoneset(self):
"""Method to deActivate the zone config."""
- return self._ssh_execute([ZoneConstant.DEACTIVATE_ZONESET], True, 1)
+ return self._ssh_execute([zone_constant.DEACTIVATE_ZONESET], True, 1)
def delete_zones(self, zone_names, activate, active_zone_set=None):
"""Delete zones from fabric.
Method to delete the active zone config zones
- params zone_names: zoneNames separated by semicolon
- params activate: True/False
- params active_zone_set: the active zone set dict retrieved
+ :param zone_names: zoneNames separated by semicolon
+ :param activate: True/False
+ :param active_zone_set: the active zone set dict retrieved
from get_active_zone_set method
"""
active_zoneset_name = None
if not active_zone_set:
active_zone_set = self.get_active_zone_set()
active_zoneset_name = active_zone_set[
- ZoneConstant.ACTIVE_ZONE_CONFIG]
- zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+ zone_constant.ACTIVE_ZONE_CONFIG]
+ zone_list = active_zone_set[zone_constant.CFG_ZONES]
zones = self.patrn.split(''.join(zone_names))
cmd = None
try:
return_list = []
try:
cmd = '%(nsshow)s;%(nscamshow)s' % {
- 'nsshow': ZoneConstant.NS_SHOW,
- 'nscamshow': ZoneConstant.NS_CAM_SHOW}
+ 'nsshow': zone_constant.NS_SHOW,
+ 'nscamshow': zone_constant.NS_CAM_SHOW}
cli_output = self._get_switch_info([cmd])
except exception.BrocadeZoningCliException:
with excutils.save_and_reraise_exception():
return return_list
def _cfg_save(self):
- self._ssh_execute([ZoneConstant.CFG_SAVE], True, 1)
+ self._ssh_execute([zone_constant.CFG_SAVE], True, 1)
def _zone_delete(self, zone_name):
cmd = 'zonedelete "%(zone_name)s"' % {'zone_name': zone_name}
def _cfg_trans_abort(self):
is_abortable = self._is_trans_abortable()
if(is_abortable):
- self.apply_zone_change([ZoneConstant.CFG_ZONE_TRANS_ABORT])
+ self.apply_zone_change([zone_constant.CFG_ZONE_TRANS_ABORT])
def _is_trans_abortable(self):
is_abortable = False
stdout, stderr = None, None
stdout, stderr = self._run_ssh(
- [ZoneConstant.CFG_SHOW_TRANS], True, 1)
+ [zone_constant.CFG_SHOW_TRANS], True, 1)
output = stdout.splitlines()
is_abortable = False
for line in output:
- if(ZoneConstant.TRANS_ABORTABLE in line):
+ if(zone_constant.TRANS_ABORTABLE in line):
is_abortable = True
break
if stderr:
None,
self.switch_user,
self.switch_pwd,
+ self.switch_key,
min_size=1,
max_size=5)
last_exception = None
None,
self.switch_user,
self.switch_pwd,
+ self.switch_key,
min_size=1,
max_size=5)
stdin, stdout, stderr = None, None, None
attempts -= 1
try:
stdin, stdout, stderr = ssh.exec_command(command)
- stdin.write("%s\n" % ZoneConstant.YES)
+ stdin.write("%s\n" % zone_constant.YES)
channel = stdout.channel
exit_status = channel.recv_exit_status()
LOG.debug("Exit Status from ssh: %s", exit_status)
def _execute_shell_cmd(self, cmd):
"""Run command over shell for older firmware versions.
- We invoke shell and issue the command and return the output.
+ Invokes shell and issue the command and return the output.
This is primarily used for issuing read commands when we are not sure
if the firmware supports exec_command.
"""
None,
self.switch_user,
self.switch_pwd,
+ self.switch_key,
min_size=1,
max_size=5)
with self.sshpool.item() as ssh:
channel.close()
except Exception:
LOG.exception(_LE('Error closing channel.'))
+ LOG.debug("_execute_cmd: stdout to return: %s", stdout)
LOG.debug("_execute_cmd: stderr to return: %s", stderr)
return (stdout, stderr)
--- /dev/null
+# (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.
+#
+
+"""
+Brocade Zone Connector Factory is responsible to dynamically create the
+connection object based on the configuration
+"""
+
+from oslo_log import log as logging
+from oslo_utils import importutils
+
+from cinder.zonemanager.drivers.brocade import fc_zone_constants
+
+LOG = logging.getLogger(__name__)
+
+
+class BrcdFCZoneFactory(object):
+
+ def __init__(self):
+ self.sb_conn_map = {}
+
+ def get_connector(self, fabric, sb_connector):
+ """Returns Device Connector.
+
+ Factory method to create and return
+ correct SB connector object based on the protocol
+ """
+
+ fabric_ip = fabric.safe_get('fc_fabric_address')
+ client = self.sb_conn_map.get(fabric_ip)
+
+ if not client:
+
+ fabric_user = fabric.safe_get('fc_fabric_user')
+ fabric_pwd = fabric.safe_get('fc_fabric_password')
+ fabric_port = fabric.safe_get('fc_fabric_port')
+ fabric_ssh_cert_path = fabric.safe_get('fc_fabric_ssh_cert_path')
+
+ LOG.debug("Client not found. Creating connection client for"
+ " %(ip)s with %(connector)s protocol "
+ "for the user %(user)s at port %(port)s.",
+ {'ip': fabric_ip,
+ 'connector': sb_connector,
+ 'user': fabric_user,
+ 'port': fabric_port})
+
+ if sb_connector.lower() in (fc_zone_constants.HTTP,
+ fc_zone_constants.HTTPS):
+ client = importutils.import_object(
+ "cinder.zonemanager.drivers.brocade."
+ "brcd_http_fc_zone_client.BrcdHTTPFCZoneClient",
+ ipaddress=fabric_ip,
+ username=fabric_user,
+ password=fabric_pwd,
+ port=fabric_port,
+ protocol=sb_connector
+ )
+ else:
+ client = importutils.import_object(
+ "cinder.zonemanager.drivers.brocade."
+ "brcd_fc_zone_client_cli.BrcdFCZoneClientCLI",
+ ipaddress=fabric_ip,
+ username=fabric_user,
+ password=fabric_pwd,
+ key=fabric_ssh_cert_path,
+ port=fabric_port
+ )
+ self.sb_conn_map.update({fabric_ip: client})
+ return client
# (c) Copyright 2015 Brocade Communications Systems Inc.
# All Rights Reserved.
#
-# Copyright 2015 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
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as fabric_opts
+from cinder.zonemanager.drivers.brocade import fc_zone_constants
from cinder.zonemanager.drivers import driver_utils
from cinder.zonemanager.drivers import fc_zone_driver
+from cinder.zonemanager import utils
LOG = logging.getLogger(__name__)
SUPPORTED_CHARS = string.ascii_letters + string.digits + '_'
brcd_opts = [
cfg.StrOpt('brcd_sb_connector',
- default='cinder.zonemanager.drivers.brocade'
- '.brcd_fc_zone_client_cli.BrcdFCZoneClientCLI',
- help='Southbound connector for zoning operation'),
+ default=fc_zone_constants.HTTP.upper(),
+ help='South bound connector for zoning operation'),
]
CONF = cfg.CONF
1.0 - Initial Brocade FC zone driver
1.1 - Implements performance enhancements
1.2 - Added support for friendly zone name
+ 1.3 - Added HTTP connector support
"""
- VERSION = "1.2"
+ VERSION = "1.3"
def __init__(self, **kwargs):
super(BrcdFCZoneDriver, self).__init__(**kwargs)
self.fabric_configs = fabric_opts.load_fabric_configurations(
fabric_names)
- def get_formatted_wwn(self, wwn_str):
- """Utility API that formats WWN to insert ':'."""
- wwn_str = wwn_str.encode('ascii')
- if len(wwn_str) != 16:
- return wwn_str
- else:
- return b':'.join(
- [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, host_name=None,
storage_system=None):
"no zoning will be performed."))
return
- cli_client = self._get_cli_client(fabric)
- cfgmap_from_fabric = self._get_active_zone_set(cli_client)
+ client = self._get_southbound_client(fabric)
+ cfgmap_from_fabric = self._get_active_zone_set(client)
zone_names = []
if cfgmap_from_fabric.get('zones'):
for initiator_key in initiator_target_map.keys():
zone_map = {}
initiator = initiator_key.lower()
- t_list = initiator_target_map[initiator_key]
+ target_list = initiator_target_map[initiator_key]
if zoning_policy == 'initiator-target':
- for t in t_list:
- target = t.lower()
- zone_members = [self.get_formatted_wwn(initiator),
- self.get_formatted_wwn(target)]
+ for target in target_list:
+ zone_members = [utils.get_formatted_wwn(initiator),
+ utils.get_formatted_wwn(target)]
zone_name = driver_utils.get_friendly_zone_name(
zoning_policy,
initiator,
storage_system,
zone_name_prefix,
SUPPORTED_CHARS)
- if (
- len(cfgmap_from_fabric) == 0 or (
+ if (len(cfgmap_from_fabric) == 0 or (
zone_name not in zone_names)):
zone_map[zone_name] = zone_members
else:
"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_members = [utils.get_formatted_wwn(initiator)]
+ for target in target_list:
+ zone_members.append(utils.get_formatted_wwn(target))
zone_name = driver_utils.get_friendly_zone_name(
zoning_policy,
if len(zone_map) > 0:
try:
- cli_client.add_zones(
+ client.add_zones(
zone_map, zone_activate,
cfgmap_from_fabric)
- cli_client.cleanup()
- except exception.BrocadeZoningCliException as brocade_ex:
+ client.cleanup()
+ except (exception.BrocadeZoningCliException,
+ exception.BrocadeZoningHttpException) as brocade_ex:
raise exception.FCZoneDriverException(brocade_ex)
except Exception:
msg = _("Failed to add zoning configuration.")
zoning_policy = zoning_policy_fab
LOG.info(_LI("Zoning policy for fabric %(policy)s"),
{'policy': zoning_policy})
- conn = self._get_cli_client(fabric)
+ conn = self._get_southbound_client(fabric)
cfgmap_from_fabric = self._get_active_zone_set(conn)
zone_names = []
{'cfgmap': cfgmap_from_fabric})
for initiator_key in initiator_target_map.keys():
initiator = initiator_key.lower()
- formatted_initiator = self.get_formatted_wwn(initiator)
+ formatted_initiator = utils.get_formatted_wwn(initiator)
zone_map = {}
zones_to_delete = []
t_list = initiator_target_map[initiator_key]
zone_members = [formatted_initiator]
for t in t_list:
target = t.lower()
- zone_members.append(self.get_formatted_wwn(target))
+ zone_members.append(utils.get_formatted_wwn(target))
zone_name = driver_utils.get_friendly_zone_name(
zoning_policy,
zone_name_string, zone_activate,
cfgmap_from_fabric)
conn.cleanup()
+ except (exception.BrocadeZoningCliException,
+ exception.BrocadeZoningHttpException) as brocade_ex:
+ raise exception.FCZoneDriverException(brocade_ex)
except Exception:
- msg = _("Failed to update or delete zoning configuration")
+ msg = _("Failed to update or delete zoning "
+ "configuration.")
LOG.exception(msg)
raise exception.FCZoneDriverException(msg)
Look up each SAN configured and return a map of SAN (fabric IP) to
list of target WWNs visible to the fabric.
"""
- # TODO(Santhosh Kolathur): consider refactoring to use lookup service.
formatted_target_list = []
fabric_map = {}
fc_fabric_names = self.configuration.fc_fabric_names
{'targetwwns': target_wwn_list})
if len(fabrics) > 0:
for t in target_wwn_list:
- formatted_target_list.append(self.get_formatted_wwn(t.lower()))
+ formatted_target_list.append(utils.get_formatted_wwn(t))
LOG.debug("Formatted target WWN list: %(targetlist)s",
{'targetlist': formatted_target_list})
for fabric_name in fabrics:
- conn = self._get_cli_client(fabric_name)
+ conn = self._get_southbound_client(fabric_name)
# Get name server data from fabric and get the targets
# logged in.
LOG.debug("Name server info from fabric: %(nsinfo)s",
{'nsinfo': nsinfo})
conn.cleanup()
- except exception.BrocadeZoningCliException:
+ except (exception.BrocadeZoningCliException,
+ exception.BrocadeZoningHttpException):
if not conn.is_supported_firmware():
msg = _("Unsupported firmware on switch %s. Make sure "
"switch is running firmware v6.4 or higher"
) % conn.switch_ip
- LOG.error(msg)
+ LOG.exception(msg)
raise exception.FCZoneDriverException(msg)
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Error getting name server info."))
cfgmap = None
try:
cfgmap = conn.get_active_zone_set()
- except exception.BrocadeZoningCliException:
+ except (exception.BrocadeZoningCliException,
+ exception.BrocadeZoningHttpException):
if not conn.is_supported_firmware():
msg = _("Unsupported firmware on switch %s. Make sure "
"switch is running firmware v6.4 or higher"
) % conn.switch_ip
LOG.error(msg)
raise exception.FCZoneDriverException(msg)
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_LE("Error getting name server info."))
except Exception as e:
msg = (_("Failed to retrieve active zoning configuration %s")
% six.text_type(e))
+ LOG.error(msg)
raise exception.FCZoneDriverException(msg)
LOG.debug("Active zone set from fabric: %(cfgmap)s",
{'cfgmap': cfgmap})
return cfgmap
- def _get_cli_client(self, fabric):
- fabric_ip = self.fabric_configs[fabric].safe_get('fc_fabric_address')
- fabric_user = self.fabric_configs[fabric].safe_get('fc_fabric_user')
- fabric_pwd = self.fabric_configs[fabric].safe_get('fc_fabric_password')
- fabric_port = self.fabric_configs[fabric].safe_get('fc_fabric_port')
- cli_client = None
+ def _get_southbound_client(self, fabric):
+ """Implementation to get SouthBound Connector.
+
+ South bound connector will be
+ dynamically selected based on the configuration
+
+ :param fabric: fabric information
+ """
+ fabric_info = self.fabric_configs[fabric]
+ fc_ip = fabric_info.safe_get('fc_fabric_address')
+ sb_connector = fabric_info.safe_get('fc_southbound_protocol')
+ if sb_connector is None:
+ sb_connector = self.configuration.brcd_sb_connector
try:
- cli_client = self.sb_conn_map.get(fabric_ip)
- if not cli_client:
- 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,
- username=fabric_user,
- password=fabric_pwd,
- port=fabric_port)
- self.sb_conn_map[fabric_ip] = cli_client
- except Exception as e:
- LOG.error(e)
- msg = _("Failed to create sb connector for %s") % fabric_ip
+ conn_factory = importutils.import_object(
+ "cinder.zonemanager.drivers.brocade."
+ "brcd_fc_zone_connector_factory."
+ "BrcdFCZoneFactory")
+ client = conn_factory.get_connector(fabric_info,
+ sb_connector.upper())
+ except Exception:
+ msg = _("Failed to create south bound connector for %s.") % fc_ip
+ LOG.exception(msg)
raise exception.FCZoneDriverException(msg)
- return cli_client
+ return client
--- /dev/null
+# (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.
+#
+"""
+Brocade south bound connector to communicate with switch using
+HTTP or HTTPS protocol.
+"""
+
+from oslo_log import log as logging
+import requests
+import six
+import time
+
+from cinder import exception
+from cinder.i18n import _
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
+
+
+LOG = logging.getLogger(__name__)
+
+
+class BrcdHTTPFCZoneClient(object):
+
+ def __init__(self, ipaddress, username,
+ password, port, protocol):
+ """Initializing the client with the parameters passed.
+
+ Creates authentication token and authenticate with switch
+ to ensure the credentials are correct.
+
+ :param ipaddress: IP Address of the device.
+ :param username: User id to login.
+ :param password: User password.
+ :param port: Device Communication port
+ :param protocol: Communication Protocol.
+ """
+ self.switch_ip = ipaddress
+ self.switch_user = username
+ self.switch_pwd = password
+ self.protocol = protocol
+ self.cfgs = {}
+ self.zones = {}
+ self.alias = {}
+ self.qlps = {}
+ self.ifas = {}
+ self.active_cfg = ''
+ self.parsed_raw_zoneinfo = ""
+ self.random_no = ''
+ self.session = None
+
+ # Create and assign the authentication header based on the credentials
+ self.auth_header = self.create_auth_token()
+
+ # Authenticate with the switch
+ # 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()
+
+ def connect(self, requestType, requestURL, payload='', header=None):
+ """Connect to the switch using HTTP/HTTPS protocol.
+
+ :param requestType: Connection Request method
+ :param requestURL: Connection URL
+ :param payload: Data to send with POST request
+ :param header: Request Headers
+
+ :returns: HTTP response data
+ :raises: BrocadeZoningHttpException
+ """
+ try:
+ if header is None:
+ header = {}
+ header.update({"User-Agent": "OpenStack Zone Driver"})
+
+ # Ensure only one connection is made throughout the life cycle
+ protocol = zone_constant.HTTP
+ if self.protocol == zone_constant.PROTOCOL_HTTPS:
+ protocol = zone_constant.HTTPS
+ if self.session is None:
+ self.session = requests.Session()
+ adapter = requests.adapters.HTTPAdapter(pool_connections=1,
+ pool_maxsize=1)
+ self.session.mount(protocol + '://', adapter)
+ url = protocol + "://" + self.switch_ip + requestURL
+ response = None
+ if requestType == zone_constant.GET_METHOD:
+ response = self.session.get(url,
+ headers=(header),
+ verify=False)
+ elif requestType == zone_constant.POST_METHOD:
+ response = self.session.post(url,
+ payload,
+ headers=(header),
+ verify=False)
+
+ # Throw exception when response status is not OK
+ if response.status_code != zone_constant.STATUS_OK:
+ msg = _("Error while querying page %(url)s on the switch, "
+ "reason %(error)s.") % {'url': url,
+ 'error': response.reason}
+ raise exception.BrocadeZoningHttpException(msg)
+ else:
+ return response.text
+ except requests.exceptions.ConnectionError as e:
+ msg = (_("Error while connecting the switch %(switch_id)s "
+ "with protocol %(protocol)s. Error: %(error)s.")
+ % {'switch_id': self.switch_ip,
+ 'protocol': self.protocol,
+ 'error': six.text_type(e)})
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ except exception.BrocadeZoningHttpException as ex:
+ msg = (_("Unexpected status code from the switch %(switch_id)s "
+ "with protocol %(protocol)s for url %(page)s. "
+ "Error: %(error)s")
+ % {'switch_id': self.switch_ip,
+ 'protocol': self.protocol,
+ 'page': requestURL,
+ 'error': six.text_type(ex)})
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def create_auth_token(self):
+ """Create the authentication token.
+
+ Creates the authentication token to use in the authentication header
+ return authentication header (Base64(username:password:random no)).
+
+ :returns: Authentication Header
+ :raises: BrocadeZoningHttpException
+ """
+ try:
+ # Send GET request to secinfo.html to get random number
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.SECINFO_PAGE)
+ parsed_data = self.get_parsed_data(response,
+ zone_constant.SECINFO_BEGIN,
+ zone_constant.SECINFO_END)
+
+ # Extract the random no from secinfo.html response
+ self.random_no = self.get_nvp_value(parsed_data,
+ zone_constant.RANDOM)
+ # Form the authentication string
+ auth_string = (self.switch_user + ":" + self.switch_pwd +
+ ":" + self.random_no)
+ auth_token = auth_string.encode(
+ "base64", "strict").strip() # encode in base64 format
+ auth_header = (zone_constant.AUTH_STRING +
+ auth_token) # Build the proper header
+ except Exception as e:
+ msg = (_("Error while creating authentication token: %s")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return auth_header
+
+ def authenticate(self):
+ """Authenticate with the switch.
+
+ Returns authentication status with modified authentication
+ header (Base64(username:xxx:random no)).
+
+ :returns: Authentication status
+ :raises: BrocadeZoningHttpException
+ """
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+ try:
+ # GET Request to authenticate.html to verify the credentials
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.AUTHEN_PAGE,
+ header=headers)
+ parsed_data = self.get_parsed_data(response,
+ zone_constant.AUTHEN_BEGIN,
+ zone_constant.AUTHEN_END)
+ isauthenticated = self.get_nvp_value(
+ parsed_data, zone_constant.AUTHENTICATED)
+ if isauthenticated == "yes":
+ # Replace password in the authentication string with xxx
+ auth_string = (self.switch_user +
+ ":" + "xxx" + ":" + self.random_no)
+ auth_token = auth_string.encode("base64", "strict").strip()
+ auth_header = zone_constant.AUTH_STRING + auth_token
+ return True, auth_header
+ else:
+ auth_error_code = self.get_nvp_value(parsed_data, "errCode")
+ msg = (_("Authentication failed, verify the switch "
+ "credentials, error code %s.") % auth_error_code)
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ except Exception as e:
+ msg = (_("Error while authenticating with switch: %s.")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def get_session_info(self):
+ """Get the session information from the switch
+
+ :returns: Connection status information.
+ """
+ try:
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+ # GET request to session.html
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.SESSION_PAGE_ACTION,
+ header=headers)
+ except Exception as e:
+ msg = (_("Error while getting session information %s.")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return response
+
+ def get_parsed_data(self, data, delim1, demil2):
+ """Return the sub string between the delimiters.
+
+ :param data: String to manipulate
+ :param delim1 : Delimiter 1
+ :param delim2 : Delimiter 2
+ :returns: substring between the delimiters
+ """
+ try:
+ start = data.index(delim1)
+ start = start + len(delim1)
+ end = data.index(demil2)
+ return data[start:end]
+ except ValueError as e:
+ msg = (_("Error while parsing the data: %s.") % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def get_nvp_value(self, data, keyname):
+ """Get the value for the key passed.
+
+ :param data: NVP to manipulate
+ :param keyname: Key name
+ :returns: value for the NVP
+ """
+ try:
+ start = data.index(keyname)
+ start = start + len(keyname)
+ temp = data[start:]
+ end = temp.index("\n")
+ return (temp[:end].lstrip('= '))
+ except ValueError as e:
+ msg = (_("Error while getting nvp value: %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."""
+
+ try:
+ self.cfgs = {}
+ self.zones = {}
+ self.active_cfg = ''
+ self.alias = {}
+ self.qlps = {}
+ self.ifas = {}
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+ # GET request to gzoneinfo.htm
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.ZONE_PAGE,
+ header=headers)
+ # get the zone string from the response
+ self.parsed_raw_zoneinfo = self.get_parsed_data(
+ response,
+ zone_constant.ZONEINFO_BEGIN,
+ zone_constant.ZONEINFO_END).strip("\n")
+ LOG.debug("Original zone string from the switch: %(zoneinfo)s",
+ {'zoneinfo': self.parsed_raw_zoneinfo})
+ # convert the zone string to list
+ zoneinfo = self.parsed_raw_zoneinfo.split()
+ i = 0
+ while i < len(zoneinfo):
+ info = zoneinfo[i]
+ # check for the cfg delimiter
+ if zone_constant.CFG_DELIM in info:
+ # extract the cfg name
+ cfg_name = info.lstrip(zone_constant.CFG_DELIM)
+ # update the dict as
+ # self.cfgs={cfg_name:zone_name1;zone_name2}
+ self.cfgs.update({cfg_name: zoneinfo[i + 1]})
+ i = i + 2
+ # check for the zone delimiter
+ elif zone_constant.ZONE_DELIM in info:
+ # extract the zone name
+ zone_name = info.lstrip(zone_constant.ZONE_DELIM)
+ # update the dict as
+ # self.zones={zone_name:members1;members2}
+ self.zones.update({zone_name: zoneinfo[i + 1]})
+ i = i + 2
+ elif zone_constant.ALIAS_DELIM in info:
+ alias_name = info.lstrip(zone_constant.ALIAS_DELIM)
+ # update the dict as
+ # self.alias={alias_name:members1;members2}
+ self.alias.update({alias_name: zoneinfo[i + 1]})
+ i = i + 2
+ # check for quickloop zones
+ elif zone_constant.QLP_DELIM in info:
+ qlp_name = info.lstrip(zone_constant.QLP_DELIM)
+ # update the map as self.qlps={qlp_name:members1;members2}
+ self.qlps.update({qlp_name: zoneinfo[i + 1]})
+ i = i + 2
+ # check for fabric assist zones
+ elif zone_constant.IFA_DELIM in info:
+ ifa_name = info.lstrip(zone_constant.IFA_DELIM)
+ # update the map as self.ifas={ifa_name:members1;members2}
+ self.ifas.update({ifa_name: zoneinfo[i + 1]})
+ i = i + 2
+ elif zone_constant.ACTIVE_CFG_DELIM in info:
+ # update the string self.active_cfg=cfg_name
+ self.active_cfg = info.lstrip(
+ zone_constant.ACTIVE_CFG_DELIM)
+ if self.active_cfg == zone_constant.DEFAULT_CFG:
+ self.active_cfg = ""
+ i = i + 2
+ else:
+ i = i + 1
+ except Exception as e:
+ msg = (_("Error while changing VF context %s.") % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def is_supported_firmware(self):
+ """Check firmware version is v6.4 or higher.
+
+ This API checks if the firmware version per the plug-in support level.
+ This only checks major and minor version.
+
+ :returns: True if firmware is supported else False.
+ :raises: BrocadeZoningHttpException
+ """
+
+ isfwsupported = False
+
+ try:
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+ # GET request to switch.html
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.SWITCH_PAGE,
+ header=headers)
+ parsed_data = self.get_parsed_data(response,
+ zone_constant.SWITCHINFO_BEGIN,
+ zone_constant.SWITCHINFO_END)
+
+ # get the firmware version nvp value
+ fwVersion = self.get_nvp_value(
+ parsed_data,
+ zone_constant.FIRMWARE_VERSION).lstrip('v')
+
+ ver = fwVersion.split(".")
+ LOG.debug("Firmware version: %(version)s.", {'version': ver})
+ if int(ver[0] + ver[1]) > 63:
+ isfwsupported = True
+
+ except Exception as e:
+ msg = (_("Error while checking the firmware version %s.")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return isfwsupported
+
+ def get_active_zone_set(self):
+ """Return the active zone configuration.
+
+ Return active zoneset from fabric. When none of the configurations
+ are active then it will return empty map.
+
+ :returns: Map -- active zone set map in the following format
+ {
+ 'zones':
+ {'openstack50060b0000c26604201900051ee8e329':
+ ['50060b0000c26604', '201900051ee8e329']
+ },
+ 'active_zone_config': 'OpenStack_Cfg'
+ }
+ :raises: BrocadeZoningHttpException
+ """
+ active_zone_set = {}
+ zones_map = {}
+ try:
+ self.get_zone_info() # get the zone information of the switch
+ if self.active_cfg != '':
+ # get the zones list of the active_Cfg
+ zones_list = self.cfgs[self.active_cfg].split(";")
+ for n in zones_list:
+ # build the zones map
+ zones_map.update(
+ {n: self.zones[n].split(";")})
+ # Format map in the correct format
+ active_zone_set = {
+ "active_zone_config": self.active_cfg, "zones": zones_map}
+ return active_zone_set
+ except Exception as e:
+ msg = (_("Failed getting active zone set from fabric %s.")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def add_zones(self, add_zones_info, activate, active_zone_set=None):
+ """Add zone configuration.
+
+ This method will add the zone configuration passed by user.
+
+ :param add_zones_info: Zone names mapped to members.
+ zone members are colon separated but case-insensitive
+ { zonename1:[zonememeber1,zonemember2,...],
+ zonename2:[zonemember1, zonemember2,...]...}
+ e.g: {'openstack50060b0000c26604201900051ee8e329':
+ ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']
+ }R
+ :param activate: True will activate the zone config.
+ :param active_zone_set: Active zone set dict retrieved from
+ get_active_zone_set method
+ :raises: BrocadeZoningHttpException
+ """
+ LOG.debug("Add zones - zones passed: %(zones)s.",
+ {'zones': add_zones_info})
+ cfg_name = zone_constant.CFG_NAME
+ cfgs = self.cfgs
+ zones = self.zones
+ alias = self.alias
+ qlps = self.qlps
+ ifas = self.ifas
+ active_cfg = self.active_cfg
+ # update the active_cfg, zones and cfgs map with new information
+ zones, cfgs, active_cfg = self.add_update_zones_cfgs(cfgs,
+ zones,
+ add_zones_info,
+ active_cfg,
+ cfg_name)
+ # Build the zonestring with updated maps
+ data = self.form_zone_string(cfgs,
+ active_cfg,
+ zones,
+ alias,
+ qlps,
+ ifas,
+ activate)
+ LOG.debug("Add zones: final zone string after applying "
+ "to the switch: %(zonestring)s", {'zonestring': data})
+ # Post the zone data to the switch
+ error_code, error_msg = self.post_zone_data(data)
+ if error_code != "0":
+ msg = (_("Applying the zones and cfgs to the switch failed "
+ "(error code=%(err_code)s error msg=%(err_msg)s.")
+ % {'err_code': error_code, 'err_msg': error_msg})
+
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def form_zone_string(self, cfgs, active_cfg,
+ zones, alias, qlps, ifas, activate):
+ """Build the zone string in the required format.
+
+ :param cfgs: cfgs map
+ :param active_cfg: Active cfg string
+ :param zones: zones map
+ :param alias: alias map
+ :param qlps: qlps map
+ :param ifas: ifas map
+ :param activate: True will activate config.
+ :returns: zonestring in the required format
+ :raises: BrocadeZoningHttpException
+ """
+ try:
+ zoneString = zone_constant.ZONE_STRING_PREFIX
+
+ # based on the activate save only will be changed
+ saveonly = "false" if activate is True else "true"
+
+ # Form the zone string based on the dictionary of each items
+ for cfg in cfgs.keys():
+ zoneString += (zone_constant.CFG_DELIM +
+ cfg + " " + cfgs.get(cfg) + " ")
+ for zone in zones.keys():
+ zoneString += (zone_constant.ZONE_DELIM +
+ zone + " " + zones.get(zone) + " ")
+ for al in alias.keys():
+ zoneString += (zone_constant.ALIAS_DELIM +
+ al + " " + alias.get(al) + " ")
+ for qlp in qlps.keys():
+ zoneString += (zone_constant.QLP_DELIM +
+ qlp + " " + qlps.get(qlp) + " ")
+ for ifa in ifas.keys():
+ zoneString += (zone_constant.IFA_DELIM +
+ ifa + " " + ifas.get(ifa) + " ")
+ # append the active_cfg string only if it is not null and activate
+ # is true
+ if active_cfg != "" and activate:
+ zoneString += (zone_constant.ACTIVE_CFG_DELIM +
+ active_cfg + " null ")
+ # Build the final zone string
+ zoneString += zone_constant.ZONE_END_DELIM + saveonly
+ except Exception as e:
+ msg = (_("Exception while forming the zone string: %s.")
+ % six.text_type(e))
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return zoneString
+
+ def add_update_zones_cfgs(self, cfgs, zones, add_zones_info,
+ active_cfg, cfg_name):
+ """Add or update the zones and cfgs map based on the new zones info.
+
+ This method will return the updated zones,cfgs and active_cfg
+
+ :param cfgs: Existing cfgs map
+ :param active_cfg: Existing Active cfg string
+ :param zones: Existing zones map
+ :param add_zones_info :Zones map to add
+ :param active_cfg :Existing active cfg
+ :param cfg_name : New cfg name
+ :returns: updated zones, zone configs map, and active_cfg
+ """
+ cfg_string = ""
+ delimiter = ""
+ zones_in_active_cfg = ""
+ try:
+ if active_cfg:
+ zones_in_active_cfg = cfgs.get(active_cfg)
+ for zone_name, members in add_zones_info.items():
+ # if new zone is not active_cfg, build the cfg string with the
+ # new zones
+ if zone_name not in zones_in_active_cfg:
+ cfg_string += delimiter + zone_name
+ delimiter = ";"
+ # update the zone string
+ # if zone name already exists and dont have the new members
+ # already
+ if (zone_name in zones and set(members)
+ != set(zones.get(zone_name).split(";"))):
+ # update the existing zone with new members
+ zones.update(
+ {zone_name: (";".join(members) +
+ ";" + zones.get(zone_name))})
+ else:
+ # add a new zone with the members
+ zones.update({zone_name: ";".join(members)})
+ # update cfg string
+ if active_cfg:
+ if cfg_string:
+ # update the existing active cfg map with cfgs string
+ cfgs.update(
+ {active_cfg: cfg_string + ";" + cfgs.get(active_cfg)})
+ else:
+ # create new cfg and update that cfgs map with the new cfg
+ active_cfg = cfg_name
+ cfgs.update({cfg_name: cfg_string})
+ except Exception as e:
+ msg = (_("Error while updating the new zones and cfgs "
+ "in the zone string. Error %(description)s.")
+ % {'description': six.text_type(e)})
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return zones, cfgs, active_cfg
+
+ def get_nameserver_info(self):
+ """Get name server data from fabric.
+
+ Return the connected node port wwn list(local
+ and remote) for the given switch fabric.
+
+ :returns: name server information.
+ """
+ nsinfo = []
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+ response = self.connect(zone_constant.GET_METHOD,
+ zone_constant.NS_PAGE,
+ header=headers) # GET request to nsinfo.html
+ parsed_raw_zoneinfo = self.get_parsed_data(
+ response,
+ zone_constant.NSINFO_BEGIN,
+ zone_constant.NSINFO_END).strip("\t\n\r")
+ # build the name server information in the correct format
+ for line in parsed_raw_zoneinfo.splitlines():
+ start_index = line.find(zone_constant.NS_DELIM) + 7
+ if start_index != -1:
+ nsinfo.extend([line[start_index:start_index + 23].strip()])
+ return nsinfo
+
+ def delete_update_zones_cfgs(
+ self, cfgs, zones,
+ delete_zones_info, active_cfg):
+ """Add or update the zones and cfgs map based on the new zones info.
+
+ Return the updated zones, cfgs and active_cfg after deleting the
+ required items.
+
+ :param cfgs: Existing cfgs map
+ :param active_cfg: Existing Active cfg string
+ :param zones: Existing zones map
+ :param delete_zones_info :Zones map to add
+ :param active_cfg :Existing active cfg
+ :returns: updated zones, zone config sets, and active zone config
+ :raises: BrocadeZoningHttpException
+ """
+ try:
+ delete_zones_info = delete_zones_info.split(";")
+ for zone in delete_zones_info:
+ # remove the zones from the zone map
+ zones.pop(zone)
+ # iterated all the cfgs, but need to check since in SSH only
+ # active cfg is iterated
+ for k, v in cfgs.items():
+ v = v.split(";")
+ if zone in v:
+ # remove the zone from the cfg string
+ v.remove(zone)
+ # if all the zones are removed, remove the cfg from the
+ # cfg map
+ if not v:
+ cfgs.pop(k)
+ # update the original cfg with the updated string
+ else:
+ cfgs[k] = ";".join(v)
+
+ # if all the zones are removed in the active_cfg, update it with
+ # empty string
+ if active_cfg not in cfgs:
+ active_cfg = ""
+ except KeyError as e:
+ msg = (_("Error while removing the zones and cfgs "
+ "in the zone string: %(description)s.")
+ % {'description': six.text_type(e)})
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+ return zones, cfgs, active_cfg
+
+ def delete_zones(self, delete_zones_info, activate, active_zone_set=None):
+ """Delete zones from fabric.
+
+ Deletes zones in the active zone config.
+
+ :param zone_names: zoneNames separated by semicolon
+ :param activate: True/False
+ :param active_zone_set: the active zone set dict retrieved
+ from get_active_zone_set method
+ """
+ cfgs = self.cfgs
+ zones = self.zones
+ alias = self.alias
+ qlps = self.qlps
+ ifas = self.ifas
+ active_cfg = self.active_cfg
+ # update the active_cfg, zones and cfgs map with required information
+ # being removed
+ zones, cfgs, active_cfg = self.delete_update_zones_cfgs(
+ cfgs,
+ zones,
+ delete_zones_info,
+ active_cfg)
+ # Build the zonestring with updated maps
+ data = self.form_zone_string(cfgs,
+ active_cfg,
+ zones,
+ alias,
+ qlps,
+ ifas,
+ activate)
+ LOG.debug("Delete zones: final zone string after applying "
+ "to the switch: %(zonestring)s", {'zonestring': data})
+ error_code, error_msg = self.post_zone_data(data)
+ if error_code != "0":
+ msg = (_("Applying the zones and cfgs to the switch failed "
+ "(error code=%(err_code)s error msg=%(err_msg)s.")
+ % {'err_code': error_code, 'err_msg': error_msg})
+ LOG.error(msg)
+ raise exception.BrocadeZoningHttpException(reason=msg)
+
+ def post_zone_data(self, data):
+ """Send POST request to the switch with the payload.
+
+ :param data: payload to be sent to switch
+ """
+
+ status = "progress"
+ parsed_data_txn = ""
+ headers = {zone_constant.AUTH_HEADER: self.auth_header}
+
+ LOG.debug("Requesting the switch with posting the zone string.")
+ # POST request to gzoneinfo with zonestring as payload
+ response = self.connect(zone_constant.POST_METHOD,
+ zone_constant.ZONE_PAGE,
+ data,
+ headers)
+ parsed_data = self.get_parsed_data(response,
+ zone_constant.ZONE_TX_BEGIN,
+ zone_constant.ZONE_TX_END)
+ transID = self.get_nvp_value(parsed_data,
+ zone_constant.ZONE_TX_ID)
+ transURL = zone_constant.ZONE_TRAN_STATUS.format(txnId=transID)
+ timeout = 360
+ sleep_time = 3
+ time_elapsed = 0
+ while(status != "done"):
+ txn_response = self.connect(
+ zone_constant.GET_METHOD, transURL, "", headers)
+ parsed_data_txn = self.get_parsed_data(txn_response,
+ zone_constant.ZONE_TX_BEGIN,
+ zone_constant.ZONE_TX_END)
+ status = self.get_nvp_value(parsed_data_txn,
+ zone_constant.ZONE_TX_STATUS)
+ time.sleep(sleep_time)
+ time_elapsed += sleep_time
+ if time_elapsed > timeout:
+ break
+ if status != "done":
+ errorCode = -1
+ errorMessage = ("Timed out, waiting for zone transaction on "
+ "the switch to complete")
+ else:
+ errorCode = self.get_nvp_value(parsed_data_txn,
+ zone_constant.ZONE_ERROR_CODE)
+ errorMessage = self.get_nvp_value(parsed_data_txn,
+ zone_constant.ZONE_ERROR_MSG)
+ return errorCode, errorMessage
+
+ def cleanup(self):
+ """Close session."""
+ self.session.close()
# (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
CFG_ZONE_TRANS_ABORT = 'cfgtransabort'
NS_SHOW = 'nsshow'
NS_CAM_SHOW = 'nscamshow'
+
+"""
+HTTPS connector constants
+"""
+AUTH_HEADER = "Authorization"
+PROTOCOL_HTTPS = "HTTPS"
+STATUS_OK = 200
+SECINFO_PAGE = "/secinfo.html"
+AUTHEN_PAGE = "/authenticate.html"
+GET_METHOD = "GET"
+POST_METHOD = "POST"
+SECINFO_BEGIN = "--BEGIN SECINFO"
+SECINFO_END = "--END SECINFO"
+RANDOM = "RANDOM"
+AUTH_STRING = "Custom_Basic " # Trailing space is required, do not remove
+AUTHEN_BEGIN = "--BEGIN AUTHENTICATE"
+AUTHEN_END = "--END AUTHENTICATE"
+AUTHENTICATED = "authenticated"
+SESSION_PAGE_ACTION = "/session.html?action=query"
+SESSION_BEGIN = "--BEGIN SESSION"
+SESSION_END = "--END SESSION"
+SESSION_PAGE = "/session.html"
+ZONEINFO_BEGIN = "--BEGIN ZONE INFO"
+ZONEINFO_END = "--END ZONE INFO"
+SWITCH_PAGE = "/switch.html"
+SWITCHINFO_BEGIN = "--BEGIN SWITCH INFORMATION"
+SWITCHINFO_END = "--END SWITCH INFORMATION"
+FIRMWARE_VERSION = "swFWVersion"
+VF_ENABLED = "vfEnabled"
+MANAGEABLE_VF = "manageableLFList"
+CHANGE_VF = ("Session=--BEGIN SESSION\n\taction=apply\n\tLFId= {vfid} "
+ "\b\t--END SESSION")
+ZONE_TRAN_STATUS = "/gzoneinfo.htm?txnId={txnId}"
+CFG_DELIM = "\x01"
+ZONE_DELIM = "\x02"
+ALIAS_DELIM = "\x03"
+QLP_DELIM = "\x04"
+ZONE_END_DELIM = "\x05&saveonly="
+IFA_DELIM = "\x06"
+ACTIVE_CFG_DELIM = "\x07"
+DEFAULT_CFG = "d__efault__Cfg"
+NS_PAGE = "/nsinfo.htm"
+NSINFO_BEGIN = "--BEGIN NS INFO"
+NSINFO_END = "--END NS INFO"
+NS_DELIM = ";N ;"
+ZONE_TX_BEGIN = "--BEGIN ZONE_TXN_INFO"
+ZONE_TX_END = "--END ZONE_TXN_INFO"
+ZONE_ERROR_CODE = "errorCode"
+ZONE_PAGE = "/gzoneinfo.htm"
+CFG_NAME = "openstack_cfg"
+ZONE_STRING_PREFIX = "zonecfginfo="
+ZONE_ERROR_MSG = "errorMessage"
+ZONE_TX_ID = "txnId"
+ZONE_TX_STATUS = "status"
+SESSION_LF_ID = "sessionLFId"
+HTTP = "http"
+HTTPS = "https"
--- /dev/null
+---
+features:
+ - HTTP connector for the Cinder Brocade FC Zone plugin.
+ This connector allows for communication
+ between the Brocade FC zone plugin and the switch
+ to be over HTTP or HTTPs. To make use of this
+ connector, the user would add a configuration
+ setting in the fabric block for a Brocade switch
+ with the name as 'fc_southbound_protocol' with
+ a value as 'HTTP' or 'HTTPS'.