Adds HTTPS southbound connector for Brocade FC Zone Driver

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
This commit is contained in:
Pradeep Sathasivam 2015-05-21 20:31:29 +05:30 committed by Angela Smith
parent c9457d1452
commit 935aa1a5b4
14 changed files with 1657 additions and 166 deletions

View File

@ -842,11 +842,15 @@ class FCSanLookupServiceException(CinderException):
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):

View File

@ -1,8 +1,6 @@
# (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
@ -143,10 +141,3 @@ class TestBrcdFCSanLookupService(brcd_lookup.BrcdFCSanLookupService,
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)

View File

@ -1,8 +1,6 @@
# (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
@ -24,9 +22,9 @@ from oslo_concurrency import processutils
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'
@ -78,7 +76,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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)
@ -178,7 +176,6 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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()
@ -192,7 +189,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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)
@ -205,7 +202,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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
@ -215,8 +212,8 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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)
@ -230,7 +227,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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()
@ -239,14 +236,14 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
@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)
@ -255,7 +252,6 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
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)

View File

@ -1,8 +1,6 @@
# (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
@ -23,6 +21,7 @@ import mock
from oslo_config import cfg
from oslo_utils import importutils
import paramiko
import requests
from cinder import exception
from cinder import test
@ -76,15 +75,16 @@ class BrcdFcZoneDriverBaseTest(object):
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'
@ -110,40 +110,60 @@ class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
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)
@ -170,12 +190,7 @@ class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
_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
@ -200,7 +215,25 @@ class FakeBrcdFCZoneClientCLI(object):
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):
@ -208,10 +241,10 @@ class FakeBrcdFCSanLookupService(object):
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,

View File

@ -0,0 +1,585 @@
# (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=openstack_cfg zone1;zone2 '\
'zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
'zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
'alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
'qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
'fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
'openstack_cfg null &saveonly=false'
mocked_zone_string_no_activate = 'zonecfginfo=openstack_cfg zone1;zone2 '\
'zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
'zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
'alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
'qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
'fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c &saveonly=true'
zone_string_to_post = "zonecfginfo=openstack_cfg "\
"openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
"zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
"zone1 20:01:00:05:33:0e:96:15;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 "\
"openstack_cfg null &saveonly=false"
zone_string_to_post_no_activate = "zonecfginfo=openstack_cfg "\
"openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
"zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
"zone1 20:01:00:05:33:0e:96:15;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 &saveonly=true"
zone_string_to_post_invalid_request = "zonecfginfo=openstack_cfg "\
"openstack50060b0000c26604201900051ee8e32900000000000000000000000000;"\
"zone1;zone2 openstack50060b0000c26604201900051ee8e329000000000000000000000"\
"00000 50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 "\
"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 &saveonly=true"
zone_string_del_to_post = "zonecfginfo=openstack_cfg zone1;zone2"\
" zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
"zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
"openstack_cfg null &saveonly=false"
zone_string_del_to_post_no_active = "zonecfginfo=openstack_cfg zone1;zone2"\
" zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
"zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 &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
openstack_cfg zone1;zone2 """\
"""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 """\
"""alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 """\
"""qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
"""fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
"""openstack_cfg null 1045274"""\
"""--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)

View File

@ -1,8 +1,6 @@
# (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
@ -16,7 +14,6 @@
# under the License.
#
"""Unit tests for fc san lookup service."""
from cinder import exception

View File

@ -1,8 +1,6 @@
# (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
@ -21,30 +19,40 @@ from oslo_log import log as logging
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
@ -59,5 +67,4 @@ def load_fabric_configurations(fabric_names):
LOG.debug("Loaded FC fabric config %(fabricname)s",
{'fabricname': fabric_name})
fabric_configs[fabric_name] = config
return fabric_configs

View File

@ -28,6 +28,7 @@ from cinder import utils
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__)
@ -97,10 +98,10 @@ class BrcdFCSanLookupService(fc_service.FCSanLookupService):
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:
@ -237,11 +238,3 @@ class BrcdFCSanLookupService(fc_service.FCSanLookupService):
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()

View File

@ -1,8 +1,6 @@
# (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
@ -34,7 +32,7 @@ from cinder import exception
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__)
@ -44,14 +42,17 @@ class BrcdFCZoneClientCLI(object):
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):
@ -77,7 +78,7 @@ class BrcdFCZoneClientCLI(object):
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 "
@ -91,7 +92,7 @@ class BrcdFCZoneClientCLI(object):
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]:
@ -101,10 +102,10 @@ class BrcdFCZoneClientCLI(object):
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,
@ -137,7 +138,7 @@ class BrcdFCZoneClientCLI(object):
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
@ -172,10 +173,10 @@ class BrcdFCZoneClientCLI(object):
# 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:
@ -197,21 +198,21 @@ class BrcdFCZoneClientCLI(object):
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
@ -219,8 +220,8 @@ class BrcdFCZoneClientCLI(object):
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:
@ -260,8 +261,8 @@ class BrcdFCZoneClientCLI(object):
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():
@ -273,7 +274,7 @@ class BrcdFCZoneClientCLI(object):
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}
@ -282,17 +283,17 @@ class BrcdFCZoneClientCLI(object):
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:
@ -392,6 +393,7 @@ class BrcdFCZoneClientCLI(object):
None,
self.switch_user,
self.switch_pwd,
self.switch_key,
min_size=1,
max_size=5)
last_exception = None
@ -438,6 +440,7 @@ class BrcdFCZoneClientCLI(object):
None,
self.switch_user,
self.switch_pwd,
self.switch_key,
min_size=1,
max_size=5)
stdin, stdout, stderr = None, None, None
@ -449,7 +452,7 @@ class BrcdFCZoneClientCLI(object):
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)
@ -499,7 +502,7 @@ class BrcdFCZoneClientCLI(object):
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.
"""
@ -512,6 +515,7 @@ class BrcdFCZoneClientCLI(object):
None,
self.switch_user,
self.switch_pwd,
self.switch_key,
min_size=1,
max_size=5)
with self.sshpool.item() as ssh:
@ -545,6 +549,7 @@ exit
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)

View File

@ -0,0 +1,82 @@
# (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

View File

@ -1,8 +1,6 @@
# (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
@ -41,17 +39,18 @@ import string
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
@ -68,9 +67,10 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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)
@ -103,15 +103,6 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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):
@ -147,8 +138,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
"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'):
@ -158,12 +149,11 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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,
@ -172,8 +162,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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:
@ -182,10 +171,9 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
"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,
@ -208,11 +196,12 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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.")
@ -248,7 +237,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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 = []
@ -262,7 +251,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
{'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]
@ -290,7 +279,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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,
@ -353,8 +342,12 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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)
@ -364,7 +357,6 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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
@ -374,11 +366,11 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
{'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.
@ -388,12 +380,13 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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."))
@ -425,41 +418,47 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
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

View File

@ -0,0 +1,734 @@
# (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()

View File

@ -1,8 +1,6 @@
# (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
@ -44,3 +42,60 @@ CFG_SHOW_TRANS = 'cfgtransshow'
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"

View File

@ -0,0 +1,10 @@
---
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'.