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:
parent
c9457d1452
commit
935aa1a5b4
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
585
cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py
Normal file
585
cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py
Normal 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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
734
cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py
Normal file
734
cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py
Normal 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()
|
@ -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"
|
||||
|
@ -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'.
|
Loading…
x
Reference in New Issue
Block a user