]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Adds HTTPS southbound connector for Brocade FC Zone Driver
authorPradeep Sathasivam <psathasi@brocade.com>
Thu, 21 May 2015 15:01:29 +0000 (20:31 +0530)
committerAngela Smith <aallen@brocade.com>
Thu, 28 Jan 2016 23:07:04 +0000 (15:07 -0800)
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

14 files changed:
cinder/exception.py
cinder/tests/unit/zonemanager/test_brcd_fc_san_lookup_service.py
cinder/tests/unit/zonemanager/test_brcd_fc_zone_client_cli.py
cinder/tests/unit/zonemanager/test_brcd_fc_zone_driver.py
cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py [new file with mode: 0644]
cinder/tests/unit/zonemanager/test_brcd_lookup_service.py
cinder/zonemanager/drivers/brocade/brcd_fabric_opts.py
cinder/zonemanager/drivers/brocade/brcd_fc_san_lookup_service.py
cinder/zonemanager/drivers/brocade/brcd_fc_zone_client_cli.py
cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py [new file with mode: 0644]
cinder/zonemanager/drivers/brocade/brcd_fc_zone_driver.py
cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py [new file with mode: 0644]
cinder/zonemanager/drivers/brocade/fc_zone_constants.py
releasenotes/notes/brocade_http_connector-0021e41dfa56e671.yaml [new file with mode: 0644]

index 1911a57e8288d3592b81fdc478a90dfbac193424..88b3c3da94fc16396861ae1c1b338be7240d3812 100644 (file)
@@ -842,11 +842,15 @@ class FCSanLookupServiceException(CinderException):
 
 
 class BrocadeZoningCliException(CinderException):
-    message = _("Fibre Channel Zoning CLI error: %(reason)s")
+    message = _("Brocade Fibre Channel Zoning CLI error: %(reason)s")
+
+
+class BrocadeZoningHttpException(CinderException):
+    message = _("Brocade Fibre Channel Zoning HTTP error: %(reason)s")
 
 
 class CiscoZoningCliException(CinderException):
-    message = _("Fibre Channel Zoning CLI error: %(reason)s")
+    message = _("Cisco Fibre Channel Zoning CLI error: %(reason)s")
 
 
 class NetAppDriverException(VolumeDriverException):
index 4598fec30f039ce07b5a8291506daea8882d9761..987980a76442dc535717f1da6351fac05fc4300e 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -143,10 +141,3 @@ class TestBrcdFCSanLookupService(brcd_lookup.BrcdFCSanLookupService,
         self.assertEqual(parsed_switch_port_wwns, return_wwn_list)
         self.assertRaises(exception.InvalidParameterValue,
                           self._parse_ns_output, invalid_switch_data)
-
-    def test_get_formatted_wwn(self):
-        wwn_list = ['10008c7cff523b01']
-        return_wwn_list = []
-        expected_wwn_list = ['10:00:8c:7c:ff:52:3b:01']
-        return_wwn_list.append(self.get_formatted_wwn(wwn_list[0]))
-        self.assertEqual(expected_wwn_list, return_wwn_list)
index b603067811b7995863db52e9a34b3a15d64d9933..94e9ece90c63d956371e84eba1ba935b17e78544 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -24,9 +22,9 @@ from oslo_concurrency import processutils
 
 from cinder import exception
 from cinder import test
-from cinder.zonemanager.drivers.brocade \
-    import brcd_fc_zone_client_cli as client_cli
-import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+from cinder.zonemanager.drivers.brocade import (brcd_fc_zone_client_cli
+                                                as client_cli)
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
 
 
 nsshow = '20:1a:00:05:1e:e8:e3:29'
@@ -78,7 +76,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
     def test_get_active_zone_set(self, get_switch_info_mock):
-        cmd_list = [ZoneConstant.GET_ACTIVE_ZONE_CFG]
+        cmd_list = [zone_constant.GET_ACTIVE_ZONE_CFG]
         get_switch_info_mock.return_value = cfgactvshow
         active_zoneset_returned = self.get_active_zone_set()
         get_switch_info_mock.assert_called_once_with(cmd_list)
@@ -178,7 +176,6 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
     def test_get_nameserver_info(self, get_switch_info_mock):
-        ns_info_list = []
         ns_info_list_expected = ['20:1a:00:05:1e:e8:e3:29']
         get_switch_info_mock.return_value = (switch_data)
         ns_info_list = self.get_nameserver_info()
@@ -192,7 +189,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_ssh_execute')
     def test__cfg_save(self, ssh_execute_mock):
-        cmd_list = [ZoneConstant.CFG_SAVE]
+        cmd_list = [zone_constant.CFG_SAVE]
         self._cfg_save()
         ssh_execute_mock.assert_called_once_with(cmd_list, True, 1)
 
@@ -205,7 +202,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
     def test__cfg_trans_abort(self, apply_zone_change_mock):
-        cmd_list = [ZoneConstant.CFG_ZONE_TRANS_ABORT]
+        cmd_list = [zone_constant.CFG_ZONE_TRANS_ABORT]
         with mock.patch.object(self, '_is_trans_abortable') \
                 as is_trans_abortable_mock:
             is_trans_abortable_mock.return_value = True
@@ -215,8 +212,8 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
     def test__is_trans_abortable_true(self, run_ssh_mock):
-        cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
-        run_ssh_mock.return_value = (Stream(ZoneConstant.TRANS_ABORTABLE),
+        cmd_list = [zone_constant.CFG_SHOW_TRANS]
+        run_ssh_mock.return_value = (Stream(zone_constant.TRANS_ABORTABLE),
                                      None)
         data = self._is_trans_abortable()
         self.assertTrue(data)
@@ -230,7 +227,7 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
     def test__is_trans_abortable_false(self, run_ssh_mock):
-        cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
+        cmd_list = [zone_constant.CFG_SHOW_TRANS]
         cfgtransshow = 'There is no outstanding zoning transaction'
         run_ssh_mock.return_value = (Stream(cfgtransshow), None)
         data = self._is_trans_abortable()
@@ -239,14 +236,14 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
     def test_apply_zone_change(self, run_ssh_mock):
-        cmd_list = [ZoneConstant.CFG_SAVE]
+        cmd_list = [zone_constant.CFG_SAVE]
         run_ssh_mock.return_value = (None, None)
         self.apply_zone_change(cmd_list)
         run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
 
     @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
     def test__get_switch_info(self, run_ssh_mock):
-        cmd_list = [ZoneConstant.NS_SHOW]
+        cmd_list = [zone_constant.NS_SHOW]
         nsshow_list = [nsshow]
         run_ssh_mock.return_value = (Stream(nsshow), Stream())
         switch_data = self._get_switch_info(cmd_list)
@@ -255,7 +252,6 @@ class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
 
     def test__parse_ns_output(self):
         invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
-        return_wwn_list = []
         expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29']
         return_wwn_list = self._parse_ns_output(switch_data)
         self.assertEqual(expected_wwn_list, return_wwn_list)
index 460b4b59556a61fe6ba19102aef69b40ed766895..e42543dc049ecb47cc91ac267a9ca098d9f9dfe1 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -23,6 +21,7 @@ import mock
 from oslo_config import cfg
 from oslo_utils import importutils
 import paramiko
+import requests
 
 from cinder import exception
 from cinder import test
@@ -76,15 +75,16 @@ class BrcdFcZoneDriverBaseTest(object):
 
         configuration.fc_fabric_names = 'BRCD_FAB_1'
         configuration.fc_fabric_address_BRCD_FAB_1 = '10.24.48.213'
-        if (is_normal):
+        configuration.fc_southbound_connector = 'CLI'
+        if is_normal:
             configuration.fc_fabric_user_BRCD_FAB_1 = 'admin'
         else:
             configuration.fc_fabric_user_BRCD_FAB_1 = 'invaliduser'
         configuration.fc_fabric_password_BRCD_FAB_1 = 'password'
 
-        if (mode == 1):
+        if mode == 1:
             configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
-        elif (mode == 2):
+        elif mode == 2:
             configuration.zoning_policy_BRCD_FAB_1 = 'initiator'
         else:
             configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
@@ -110,40 +110,60 @@ class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
     def fake__get_active_zone_set(self, brcd_sb_connector, fabric_ip):
         return GlobalVars._active_cfg
 
+    def get_client(self, protocol='HTTPS'):
+        conn = ('cinder.tests.unit.zonemanager.test_brcd_fc_zone_driver.' +
+                ('FakeBrcdFCZoneClientCLI' if protocol == "CLI"
+                 else 'FakeBrcdHttpFCZoneClient'))
+        client = importutils.import_object(
+            conn,
+            ipaddress="10.24.48.213",
+            username="admin",
+            password="password",
+            key="/home/stack/.ssh/id_rsa",
+            port=22,
+            protocol=protocol
+        )
+        return client
+
     def fake_get_san_context(self, target_wwn_list):
         fabric_map = {}
         return fabric_map
 
-    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
-    def test_add_connection(self, get_active_zs_mock):
+    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+    def test_add_connection(self, get_southbound_client_mock):
         """Normal flow for i-t mode."""
         GlobalVars._is_normal_test = True
         GlobalVars._zone_state = []
-        get_active_zs_mock.return_value = _active_cfg_before_add
+        GlobalVars._active_cfg = _active_cfg_before_add
+        get_southbound_client_mock.return_value = self.get_client("HTTPS")
         self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
         self.assertTrue(_zone_name in GlobalVars._zone_state)
 
-    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
-    def test_delete_connection(self, get_active_zs_mock):
+    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+    def test_delete_connection(self, get_southbound_client_mock):
         GlobalVars._is_normal_test = True
-        get_active_zs_mock.return_value = _active_cfg_before_delete
+        get_southbound_client_mock.return_value = self.get_client("CLI")
+        GlobalVars._active_cfg = _active_cfg_before_delete
         self.driver.delete_connection(
             'BRCD_FAB_1', _initiator_target_map)
         self.assertFalse(_zone_name in GlobalVars._zone_state)
 
-    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
-    def test_add_connection_for_initiator_mode(self, get_active_zs_mock):
+    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+    def test_add_connection_for_initiator_mode(self, get_southbound_client_mk):
         """Normal flow for i mode."""
         GlobalVars._is_normal_test = True
-        get_active_zs_mock.return_value = _active_cfg_before_add
+        get_southbound_client_mk.return_value = self.get_client("CLI")
+        GlobalVars._active_cfg = _active_cfg_before_add
         self.setup_driver(self.setup_config(True, 2))
         self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
         self.assertTrue(_zone_name in GlobalVars._zone_state)
 
-    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_active_zone_set')
-    def test_delete_connection_for_initiator_mode(self, get_active_zs_mock):
+    @mock.patch.object(driver.BrcdFCZoneDriver, '_get_southbound_client')
+    def test_delete_connection_for_initiator_mode(self,
+                                                  get_southbound_client_mk):
         GlobalVars._is_normal_test = True
-        get_active_zs_mock.return_value = _active_cfg_before_delete
+        get_southbound_client_mk.return_value = self.get_client("HTTPS")
+        GlobalVars._active_cfg = _active_cfg_before_delete
         self.setup_driver(self.setup_config(True, 2))
         self.driver.delete_connection(
             'BRCD_FAB_1', _initiator_target_map)
@@ -170,12 +190,7 @@ class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
                           _initiator_target_map)
 
 
-class FakeBrcdFCZoneClientCLI(object):
-    def __init__(self, ipaddress, username, password, port):
-        self.firmware_supported = True
-        if not GlobalVars._is_normal_test:
-            raise paramiko.SSHException("Unable to connect to fabric")
-
+class FakeClient(object):
     def get_active_zone_set(self):
         return GlobalVars._active_cfg
 
@@ -200,7 +215,25 @@ class FakeBrcdFCZoneClientCLI(object):
         pass
 
 
+class FakeBrcdFCZoneClientCLI(FakeClient):
+    def __init__(self, ipaddress, username,
+                 password, port, key, protocol):
+        self.firmware_supported = True
+        if not GlobalVars._is_normal_test:
+            raise paramiko.SSHException("Unable to connect to fabric.")
+
+
+class FakeBrcdHttpFCZoneClient(FakeClient):
+
+    def __init__(self, ipaddress, username,
+                 password, port, key, protocol):
+        self.firmware_supported = True
+        if not GlobalVars._is_normal_test:
+            raise requests.exception.HTTPError("Unable to connect to fabric")
+
+
 class FakeBrcdFCSanLookupService(object):
+
     def get_device_mapping_from_network(self,
                                         initiator_wwn_list,
                                         target_wwn_list):
@@ -208,10 +241,10 @@ class FakeBrcdFCSanLookupService(object):
         initiators = []
         targets = []
         for i in initiator_wwn_list:
-            if (i in _initiator_ns_map[_fabric_wwn]):
+            if i in _initiator_ns_map[_fabric_wwn]:
                 initiators.append(i)
         for t in target_wwn_list:
-            if (t in _target_ns_map[_fabric_wwn]):
+            if t in _target_ns_map[_fabric_wwn]:
                 targets.append(t)
         device_map[_fabric_wwn] = {
             'initiator_port_wwn_list': initiators,
diff --git a/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py b/cinder/tests/unit/zonemanager/test_brcd_http_fc_zone_client.py
new file mode 100644 (file)
index 0000000..67872f0
--- /dev/null
@@ -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=\ 1openstack_cfg zone1;zone2 '\
+    '\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
+    '\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
+    '\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
+    '\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+    '\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+    '\aopenstack_cfg null \ 5&saveonly=false'
+mocked_zone_string_no_activate = 'zonecfginfo=\ 1openstack_cfg zone1;zone2 '\
+    '\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 '\
+    '\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 '\
+    '\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 '\
+    '\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c '\
+    '\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c \ 5&saveonly=true'
+zone_string_to_post = "zonecfginfo=\ 1openstack_cfg "\
+    "openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
+    "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+    "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+    "\ 2openstack50060b0000c26604201900051ee8e329 "\
+    "50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 "\
+    "\aopenstack_cfg null \ 5&saveonly=false"
+zone_string_to_post_no_activate = "zonecfginfo=\ 1openstack_cfg "\
+    "openstack50060b0000c26604201900051ee8e329;zone1;zone2 "\
+    "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+    "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+    "\ 2openstack50060b0000c26604201900051ee8e329 "\
+    "50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 \ 5&saveonly=true"
+zone_string_to_post_invalid_request = "zonecfginfo=\ 1openstack_cfg "\
+    "openstack50060b0000c26604201900051ee8e32900000000000000000000000000;"\
+    "zone1;zone2 \ 2openstack50060b0000c26604201900051ee8e329000000000000000000000"\
+    "00000 50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29 "\
+    "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+    "\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 \ 5&saveonly=true"
+zone_string_del_to_post = "zonecfginfo=\ 1openstack_cfg zone1;zone2"\
+    " \ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+    "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 "\
+    "\aopenstack_cfg null \ 5&saveonly=false"
+zone_string_del_to_post_no_active = "zonecfginfo=\ 1openstack_cfg zone1;zone2"\
+    " \ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 "\
+    "\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 \ 5&saveonly=true"
+zone_post_page = """
+<BODY>
+<PRE>
+--BEGIN ZONE_TXN_INFO
+txnId=34666
+adId=0
+user=admin
+roleUser=admin
+openTxnOwner=
+openTxnId=0
+openTxnAbortable=0
+txnStarttime=1421916354
+txnEndtime=1421916355
+currStateInt=4
+prevStateInt=3
+actionInt=5
+currState=done
+prevState=progress
+action=error
+sessionId=5892021
+selfAborted=false
+status=done
+errorCode=-1
+errorMessage=Name too long
+--END ZONE_TXN_INFO
+</PRE>
+</BODY>"""
+zone_post_page_no_error = """
+<BODY>
+<PRE>
+--BEGIN ZONE_TXN_INFO
+txnId=34666
+adId=0
+user=admin
+roleUser=admin
+openTxnOwner=
+openTxnId=0
+openTxnAbortable=0
+txnStarttime=1421916354
+txnEndtime=1421916355
+currStateInt=4
+prevStateInt=3
+actionInt=5
+currState=done
+prevState=progress
+action=error
+sessionId=5892021
+selfAborted=false
+status=done
+errorCode=0
+errorMessage=
+--END ZONE_TXN_INFO
+</PRE>
+</BODY>"""
+secinfo_resp = """
+<BODY>
+<PRE>
+--BEGIN SECINFO
+SECURITY = OFF
+RANDOM = 6281590
+DefaultPasswdBitmap = 0
+primaryFCS = no
+switchType = 66
+resource = 10.24.48.210
+REALM = FC Switch Administration
+AUTHMETHOD = Custom_Basic
+hasUpfrontLogin=yes
+AUTHVERSION = 1
+vfEnabled=false
+vfSupported=true
+--END SECINFO
+</PRE>
+</BODY>
+"""
+authenticate_resp = """<HTML>
+<PRE>
+--BEGIN AUTHENTICATE
+authenticated = yes
+username=admin
+userrole=admin
+adCapable=1
+currentAD=AD0
+trueADEnvironment=0
+adId=0
+adList=ALL
+contextType=0
+--END AUTHENTICATE
+</PRE>
+</BODY>
+"""
+un_authenticate_resp = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>Authentication</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN AUTHENTICATE
+authenticated = no
+errCode = -3
+authType = Custom_Basic
+realm = FC Switch Administration
+--END AUTHENTICATE
+</PRE>
+</BODY>
+</HTML>"""
+switch_page_resp = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN SWITCH INFORMATION
+didOffset=96
+swFWVersion=v7.3.0b_rc1_bld06
+swDomain=2
+--END SWITCH INFORMATION
+</PRE>
+</BODY>
+</HTML>
+"""
+switch_page_invalid_firm = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN SWITCH INFORMATION
+didOffset=96
+swFWVersion=v6.1.1
+swDomain=2
+--END SWITCH INFORMATION
+</PRE>
+</BODY>
+</HTML>
+"""
+parsed_value = """
+didOffset=96
+swFWVersion=v7.3.0b_rc1_bld06
+swDomain=2
+"""
+zone_info = """<HTML>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>Zone Configuration Information</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+--BEGIN ZONE CHANGE
+LastZoneChangeTime=1421926251
+--END ZONE CHANGE
+isZoneTxnSupported=true
+ZoneLicense=true
+QuickLoopLicense=true
+DefZoneStatus=noaccess
+McDataDefaultZone=false
+McDataSafeZone=false
+AvailableZoneSize=1043890
+--BEGIN ZONE INFO
+\ 1openstack_cfg zone1;zone2 """\
+"""\ 2zone1 20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11 """\
+    """\ 2zone2 20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11 """\
+    """\ 3alia1 10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12 """\
+    """\ 4qlp 10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
+    """\ 6fa1 20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c """\
+    """\aopenstack_cfg null \ 51045274"""\
+    """--END ZONE INFO
+</PRE>
+</BODY>
+</HTML>
+
+"""
+
+active_zone_set = {
+    'zones':
+    {'zone1':
+     ['20:01:00:05:33:0e:96:15', '20:00:00:05:33:0e:93:11'],
+     'zone2':
+     ['20:01:00:05:33:0e:96:14', '20:00:00:05:33:0e:93:11']},
+    'active_zone_config': 'openstack_cfg'}
+updated_zones = {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+                 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+                 'test_updated_zone':
+                 '20:01:00:05:33:0e:96:10;20:00:00:05:33:0e:93:11'}
+updated_cfgs = {'openstack_cfg': 'test_updated_zone;zone1;zone2'}
+valid_zone_name = "openstack50060b0000c26604201900051ee8e329"
+
+
+class TestBrcdHttpFCZoneClient(client.BrcdHTTPFCZoneClient, test.TestCase):
+
+    def setUp(self):
+        self.auth_header = "YWRtaW46cGFzc3dvcmQ6NDM4ODEyNTIw"
+        self.switch_user = "admin"
+        self.switch_pwd = "password"
+        self.protocol = "HTTPS"
+        self.conn = None
+        self.alias = {}
+        self.qlps = {}
+        self.ifas = {}
+        self.parsed_raw_zoneinfo = ""
+        self.random_no = ''
+        self.session = None
+        super(TestBrcdHttpFCZoneClient, self).setUp()
+
+    # override some of the functions
+    def __init__(self, *args, **kwargs):
+        test.TestCase.__init__(self, *args, **kwargs)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_create_auth_token(self, connect_mock):
+        connect_mock.return_value = secinfo_resp
+        self.assertEqual("Custom_Basic YWRtaW46cGFzc3dvcmQ6NjI4MTU5MA==",
+                         self.create_auth_token())
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_authenticate(self, connect_mock):
+        connect_mock.return_value = authenticate_resp
+        self.assertEqual(
+            (True, "Custom_Basic YWRtaW46eHh4Og=="), self.authenticate())
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_authenticate_failed(self, connect_mock):
+        connect_mock.return_value = un_authenticate_resp
+        self.assertRaises(
+            exception.BrocadeZoningHttpException, self.authenticate)
+
+    def test_get_parsed_data(self):
+        valid_delimiter1 = zone_constant.SWITCHINFO_BEGIN
+        valid_delimiter2 = zone_constant.SWITCHINFO_END
+        invalid_delimiter = "--END SWITCH INFORMATION1"
+        self.assertEqual(parsed_value, self.get_parsed_data(
+            switch_page_resp, valid_delimiter1, valid_delimiter2))
+        self.assertRaises(exception.BrocadeZoningHttpException,
+                          self.get_parsed_data,
+                          switch_page_resp,
+                          valid_delimiter1,
+                          invalid_delimiter)
+        self.assertRaises(exception.BrocadeZoningHttpException,
+                          self.get_parsed_data,
+                          switch_page_resp,
+                          invalid_delimiter,
+                          valid_delimiter2)
+
+    def test_get_nvp_value(self):
+        valid_keyname = zone_constant.FIRMWARE_VERSION
+        invalid_keyname = "swFWVersion1"
+        self.assertEqual(
+            "v7.3.0b_rc1_bld06", self.get_nvp_value(parsed_value,
+                                                    valid_keyname))
+        self.assertRaises(exception.BrocadeZoningHttpException,
+                          self.get_nvp_value,
+                          parsed_value,
+                          invalid_keyname)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_is_supported_firmware(self, connect_mock):
+        connect_mock.return_value = switch_page_resp
+        self.assertTrue(self.is_supported_firmware())
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_is_supported_firmware_invalid(self, connect_mock):
+        connect_mock.return_value = switch_page_invalid_firm
+        self.assertFalse(self.is_supported_firmware())
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_get_active_zone_set(self, connect_mock):
+        connect_mock.return_value = zone_info
+        returned_zone_map = self.get_active_zone_set()
+        self.assertDictMatch(active_zone_set, returned_zone_map)
+
+    def test_form_zone_string(self):
+        new_alias = {
+            'alia1': '10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12'}
+        new_qlps = {'qlp': '10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c'}
+        new_ifas = {'fa1': '20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c'}
+        self.assertEqual(mocked_zone_string, self.form_zone_string(
+            cfgs, active_cfg, zones, new_alias, new_qlps, new_ifas, True))
+        self.assertEqual(mocked_zone_string_no_activate, self.form_zone_string(
+            cfgs, active_cfg, zones, new_alias, new_qlps, new_ifas, False))
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_add_zones_activate(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("0", "")
+        self.cfgs = cfgs.copy()
+        self.zones = zones.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        add_zones_info = {valid_zone_name:
+                          ['50:06:0b:00:00:c2:66:04',
+                              '20:19:00:05:1e:e8:e3:29']
+                          }
+        self.add_zones(add_zones_info, True)
+        post_zone_data_mock.assert_called_once_with(zone_string_to_post)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_add_zones_invalid_zone_name(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("-1", "Name Too Long")
+        self.cfgs = cfgs.copy()
+        self.zones = zones.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        invalid_zone_name = valid_zone_name + "00000000000000000000000000"
+        add_zones_info = {invalid_zone_name:
+                          ['50:06:0b:00:00:c2:66:04',
+                              '20:19:00:05:1e:e8:e3:29']
+                          }
+        self.assertRaises(
+            exception.BrocadeZoningHttpException,
+            self.add_zones, add_zones_info, False)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_add_zones_no_activate(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("0", "")
+        self.cfgs = cfgs.copy()
+        self.zones = zones.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        add_zones_info = {valid_zone_name:
+                          ['50:06:0b:00:00:c2:66:04',
+                              '20:19:00:05:1e:e8:e3:29']
+                          }
+        self.add_zones(add_zones_info, False)
+        post_zone_data_mock.assert_called_once_with(
+            zone_string_to_post_no_activate)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_delete_zones_activate(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("0", "")
+        self.cfgs = cfgs_to_delete.copy()
+        self.zones = zones_to_delete.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        delete_zones_info = valid_zone_name
+
+        self.delete_zones(delete_zones_info, True)
+        post_zone_data_mock.assert_called_once_with(zone_string_del_to_post)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_delete_zones_no_activate(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("0", "")
+        self.cfgs = cfgs_to_delete.copy()
+        self.zones = zones_to_delete.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        delete_zones_info = valid_zone_name
+        self.delete_zones(delete_zones_info, False)
+        post_zone_data_mock.assert_called_once_with(
+            zone_string_del_to_post_no_active)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'post_zone_data')
+    def test_delete_zones_invalid_zone_name(self, post_zone_data_mock):
+        post_zone_data_mock.return_value = ("0", "")
+        self.cfgs = cfgs_to_delete.copy()
+        self.zones = zones_to_delete.copy()
+        self.alias = alias.copy()
+        self.qlps = qlps.copy()
+        self.ifas = ifas.copy()
+        self.active_cfg = active_cfg
+        delete_zones_info = 'openstack50060b0000c26604201900051ee8e32'
+        self.assertRaises(exception.BrocadeZoningHttpException,
+                          self.delete_zones, delete_zones_info, False)
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_post_zone_data(self, connect_mock):
+        connect_mock.return_value = zone_post_page
+        self.assertEqual(
+            ("-1", "Name too long"), self.post_zone_data(zone_string_to_post))
+        connect_mock.return_value = zone_post_page_no_error
+        self.assertEqual(("0", ""), self.post_zone_data(zone_string_to_post))
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_get_nameserver_info(self, connect_mock):
+        connect_mock.return_value = nameserver_info
+        self.assertEqual(ns_info, self.get_nameserver_info())
+
+    def test_delete_update_zones_cfgs(self):
+
+        cfgs = {'openstack_cfg': 'zone1;zone2'}
+        zones = {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+                 'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'}
+        delete_zones_info = valid_zone_name
+        self.assertEqual(
+            (zones, cfgs, active_cfg),
+            self.delete_update_zones_cfgs(
+                cfgs_to_delete.copy(),
+                zones_to_delete.copy(),
+                delete_zones_info,
+                active_cfg))
+
+        cfgs = {'openstack_cfg': 'zone2'}
+        zones = {'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'}
+        delete_zones_info = valid_zone_name + ";zone1"
+        self.assertEqual(
+            (zones, cfgs, active_cfg),
+            self.delete_update_zones_cfgs(
+                cfgs_to_delete.copy(),
+                zones_to_delete.copy(),
+                delete_zones_info,
+                active_cfg))
+
+    def test_add_update_zones_cfgs(self):
+        add_zones_info = {valid_zone_name:
+                          ['50:06:0b:00:00:c2:66:04',
+                              '20:19:00:05:1e:e8:e3:29']
+                          }
+        updated_cfgs = {
+            'openstack_cfg':
+                valid_zone_name + ';zone1;zone2'}
+        updated_zones = {
+            'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+            'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+            valid_zone_name:
+            '50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29'}
+        self.assertEqual((updated_zones, updated_cfgs, active_cfg),
+                         self.add_update_zones_cfgs(
+                         cfgs.copy(),
+                         zones.copy(),
+                         add_zones_info,
+                         active_cfg,
+                         "openstack_cfg"))
+
+        add_zones_info = {valid_zone_name:
+                          ['50:06:0b:00:00:c2:66:04',
+                              '20:19:00:05:1e:e8:e3:29'],
+                          'test4':
+                          ['20:06:0b:00:00:b2:66:07',
+                              '20:10:00:05:1e:b8:c3:19']
+                          }
+        updated_cfgs = {
+            'openstack_cfg':
+                'test4;openstack50060b0000c26604201900051ee8e329;zone1;zone2'}
+        updated_zones = {
+            'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+            'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11',
+            valid_zone_name:
+            '50:06:0b:00:00:c2:66:04;20:19:00:05:1e:e8:e3:29',
+            'test4': '20:06:0b:00:00:b2:66:07;20:10:00:05:1e:b8:c3:19'}
+        self.assertEqual(
+            (updated_zones, updated_cfgs, active_cfg),
+            self.add_update_zones_cfgs(
+                cfgs.copy(), zones.copy(), add_zones_info,
+                active_cfg, "openstack_cfg"))
+
+    @patch.object(client.BrcdHTTPFCZoneClient, 'connect')
+    def test_get_zone_info(self, connect_mock):
+        connect_mock.return_value = zone_info
+        self.get_zone_info()
+        self.assertEqual({'openstack_cfg': 'zone1;zone2'}, self.cfgs)
+        self.assertEqual(
+            {'zone1': '20:01:00:05:33:0e:96:15;20:00:00:05:33:0e:93:11',
+             'zone2': '20:01:00:05:33:0e:96:14;20:00:00:05:33:0e:93:11'},
+            self.zones)
+        self.assertEqual('openstack_cfg', self.active_cfg)
+        self.assertEqual(
+            {'alia1': '10:00:00:05:1e:7c:64:96;10:21:10:05:33:0e:96:12'},
+            self.alias)
+        self.assertEqual(
+            {'fa1': '20:15:f4:ce:96:ae:68:6c;20:11:f4:ce:46:ae:68:6c'},
+            self.ifas)
+        self.assertEqual(
+            {'qlp': '10:11:f4:ce:46:ae:68:6c;20:11:f4:ce:46:ae:68:6c'},
+            self.qlps)
index 8a5d076f4740e1c3eac158045edb686294052141..903482a8f83332a33ee5588b21d5969127f5be15 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2013 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -16,7 +14,6 @@
 #    under the License.
 #
 
-
 """Unit tests for fc san lookup service."""
 
 from cinder import exception
index c6bb348273cbfca5f81d9697c614e24192dd6338..7cb11f73b54f2c32424b91f887e5f7ebecdbd8f1 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -21,30 +19,40 @@ from oslo_log import log as logging
 from cinder.volume import configuration
 
 brcd_zone_opts = [
+    cfg.StrOpt('fc_southbound_protocol',
+               default='HTTP',
+               choices=('SSH', 'HTTP', 'HTTPS'),
+               help='South bound connector for the fabric.'),
     cfg.StrOpt('fc_fabric_address',
                default='',
-               help='Management IP of fabric'),
+               help='Management IP of fabric.'),
     cfg.StrOpt('fc_fabric_user',
                default='',
-               help='Fabric user ID'),
+               help='Fabric user ID.'),
     cfg.StrOpt('fc_fabric_password',
                default='',
-               help='Password for user',
+               help='Password for user.',
                secret=True),
     cfg.PortOpt('fc_fabric_port',
                 default=22,
                 help='Connecting port'),
+    cfg.StrOpt('fc_fabric_ssh_cert_path',
+               default='',
+               help='Local SSH certificate Path.'),
     cfg.StrOpt('zoning_policy',
                default='initiator-target',
-               help='overridden zoning policy'),
+               help='Overridden zoning policy.'),
     cfg.BoolOpt('zone_activate',
                 default=True,
-                help='overridden zoning activation state'),
+                help='Overridden zoning activation state.'),
     cfg.StrOpt('zone_name_prefix',
                default='openstack',
-               help='overridden zone name prefix'),
+               help='Overridden zone name prefix.'),
     cfg.StrOpt('principal_switch_wwn',
-               help='Principal switch WWN of the fabric'),
+               default=None,
+               deprecated_for_removal=True,
+               help='Principal switch WWN of the fabric. This option is not '
+               'used anymore.')
 ]
 
 CONF = cfg.CONF
@@ -59,5 +67,4 @@ def load_fabric_configurations(fabric_names):
         LOG.debug("Loaded FC fabric config %(fabricname)s",
                   {'fabricname': fabric_name})
         fabric_configs[fabric_name] = config
-
     return fabric_configs
index 05c24a422263d8ec655782700e7d389e37630684..e7ac96c354a18865c00de20745c520cc3e9154e1 100644 (file)
@@ -28,6 +28,7 @@ from cinder import utils
 from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as fabric_opts
 import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
 from cinder.zonemanager import fc_san_lookup_service as fc_service
+from cinder.zonemanager import utils as fczm_utils
 
 LOG = logging.getLogger(__name__)
 
@@ -97,10 +98,10 @@ class BrcdFCSanLookupService(fc_service.FCSanLookupService):
         LOG.debug("FC Fabric List: %s", fabrics)
         if fabrics:
             for t in target_wwn_list:
-                formatted_target_list.append(self.get_formatted_wwn(t))
+                formatted_target_list.append(fczm_utils.get_formatted_wwn(t))
 
             for i in initiator_wwn_list:
-                formatted_initiator_list.append(self.
+                formatted_initiator_list.append(fczm_utils.
                                                 get_formatted_wwn(i))
 
             for fabric_name in fabrics:
@@ -237,11 +238,3 @@ class BrcdFCSanLookupService(fc_service.FCSanLookupService):
                 LOG.error(msg)
                 raise exception.InvalidParameterValue(err=msg)
         return nsinfo_list
-
-    def get_formatted_wwn(self, wwn_str):
-        """Utility API that formats WWN to insert ':'."""
-        if (len(wwn_str) != 16):
-            return wwn_str.lower()
-        else:
-            return (':'.join([wwn_str[i:i + 2]
-                              for i in range(0, len(wwn_str), 2)])).lower()
index 1e6174e772e388658f66c0089f5ec0f98685f15d..817e294d1ef30d1f3560207e283556ff1548370c 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -34,7 +32,7 @@ from cinder import exception
 from cinder.i18n import _, _LE
 from cinder import ssh_utils
 from cinder import utils
-import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
 
 LOG = logging.getLogger(__name__)
 
@@ -44,14 +42,17 @@ class BrcdFCZoneClientCLI(object):
     switch_port = '22'
     switch_user = 'admin'
     switch_pwd = 'none'
+    switch_key = 'none'
     patrn = re.compile('[;\s]+')
 
-    def __init__(self, ipaddress, username, password, port):
-        """initializing the client."""
+    def __init__(self, ipaddress, username,
+                 password, port, key):
+        """Initializing the client."""
         self.switch_ip = ipaddress
         self.switch_port = port
         self.switch_user = username
         self.switch_pwd = password
+        self.switch_key = key
         self.sshpool = None
 
     def get_active_zone_set(self):
@@ -77,7 +78,7 @@ class BrcdFCZoneClientCLI(object):
         zone_set_name = None
         try:
             switch_data = self._get_switch_info(
-                [ZoneConstant.GET_ACTIVE_ZONE_CFG])
+                [zone_constant.GET_ACTIVE_ZONE_CFG])
         except exception.BrocadeZoningCliException:
             with excutils.save_and_reraise_exception():
                 LOG.error(_LE("Failed getting active zone set "
@@ -91,7 +92,7 @@ class BrcdFCZoneClientCLI(object):
                     line_split = [x.replace(
                         ' ',
                         '') for x in line_split]
-                    if ZoneConstant.CFG_ZONESET in line_split:
+                    if zone_constant.CFG_ZONESET in line_split:
                         zone_set_name = line_split[1]
                         continue
                     if line_split[1]:
@@ -101,10 +102,10 @@ class BrcdFCZoneClientCLI(object):
                         zone_member = line_split[2]
                         zone_member_list = zone.get(zone_name)
                         zone_member_list.append(zone_member)
-            zone_set[ZoneConstant.CFG_ZONES] = zone
-            zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG] = zone_set_name
+            zone_set[zone_constant.CFG_ZONES] = zone
+            zone_set[zone_constant.ACTIVE_ZONE_CONFIG] = zone_set_name
         except Exception:
-            # Incase of parsing error here, it should be malformed cli output.
+            # In case of parsing error here, it should be malformed cli output.
             msg = _("Malformed zone configuration: (switch=%(switch)s "
                     "zone_config=%(zone_config)s)."
                     ) % {'switch': self.switch_ip,
@@ -137,7 +138,7 @@ class BrcdFCZoneClientCLI(object):
         if not active_zone_set:
             active_zone_set = self.get_active_zone_set()
             LOG.debug("Active zone set: %s", active_zone_set)
-        zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+        zone_list = active_zone_set[zone_constant.CFG_ZONES]
         LOG.debug("zone list: %s", zone_list)
         for zone in zones.keys():
             # If zone exists, its an update. Delete & insert
@@ -172,10 +173,10 @@ class BrcdFCZoneClientCLI(object):
             # Get active zone set from device, as some of the zones
             # could be deleted.
             active_zone_set = self.get_active_zone_set()
-            cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
+            cfg_name = active_zone_set[zone_constant.ACTIVE_ZONE_CONFIG]
             cmd = None
             if not cfg_name:
-                cfg_name = ZoneConstant.OPENSTACK_CFG_NAME
+                cfg_name = zone_constant.OPENSTACK_CFG_NAME
                 cmd = 'cfgcreate "%(zoneset)s", "%(zones)s"' \
                     % {'zoneset': cfg_name, 'zones': zone_with_sep}
             else:
@@ -197,21 +198,21 @@ class BrcdFCZoneClientCLI(object):
 
     def activate_zoneset(self, cfgname):
         """Method to Activate the zone config. Param cfgname - ZonesetName."""
-        cmd_list = [ZoneConstant.ACTIVATE_ZONESET, cfgname]
+        cmd_list = [zone_constant.ACTIVATE_ZONESET, cfgname]
         return self._ssh_execute(cmd_list, True, 1)
 
     def deactivate_zoneset(self):
         """Method to deActivate the zone config."""
-        return self._ssh_execute([ZoneConstant.DEACTIVATE_ZONESET], True, 1)
+        return self._ssh_execute([zone_constant.DEACTIVATE_ZONESET], True, 1)
 
     def delete_zones(self, zone_names, activate, active_zone_set=None):
         """Delete zones from fabric.
 
         Method to delete the active zone config zones
 
-        params zone_names: zoneNames separated by semicolon
-        params activate: True/False
-        params active_zone_set: the active zone set dict retrieved
+        :param zone_names: zoneNames separated by semicolon
+        :param activate: True/False
+        :param active_zone_set: the active zone set dict retrieved
                                 from get_active_zone_set method
         """
         active_zoneset_name = None
@@ -219,8 +220,8 @@ class BrcdFCZoneClientCLI(object):
         if not active_zone_set:
             active_zone_set = self.get_active_zone_set()
         active_zoneset_name = active_zone_set[
-            ZoneConstant.ACTIVE_ZONE_CONFIG]
-        zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+            zone_constant.ACTIVE_ZONE_CONFIG]
+        zone_list = active_zone_set[zone_constant.CFG_ZONES]
         zones = self.patrn.split(''.join(zone_names))
         cmd = None
         try:
@@ -260,8 +261,8 @@ class BrcdFCZoneClientCLI(object):
         return_list = []
         try:
             cmd = '%(nsshow)s;%(nscamshow)s' % {
-                'nsshow': ZoneConstant.NS_SHOW,
-                'nscamshow': ZoneConstant.NS_CAM_SHOW}
+                'nsshow': zone_constant.NS_SHOW,
+                'nscamshow': zone_constant.NS_CAM_SHOW}
             cli_output = self._get_switch_info([cmd])
         except exception.BrocadeZoningCliException:
             with excutils.save_and_reraise_exception():
@@ -273,7 +274,7 @@ class BrcdFCZoneClientCLI(object):
         return return_list
 
     def _cfg_save(self):
-        self._ssh_execute([ZoneConstant.CFG_SAVE], True, 1)
+        self._ssh_execute([zone_constant.CFG_SAVE], True, 1)
 
     def _zone_delete(self, zone_name):
         cmd = 'zonedelete "%(zone_name)s"' % {'zone_name': zone_name}
@@ -282,17 +283,17 @@ class BrcdFCZoneClientCLI(object):
     def _cfg_trans_abort(self):
         is_abortable = self._is_trans_abortable()
         if(is_abortable):
-            self.apply_zone_change([ZoneConstant.CFG_ZONE_TRANS_ABORT])
+            self.apply_zone_change([zone_constant.CFG_ZONE_TRANS_ABORT])
 
     def _is_trans_abortable(self):
         is_abortable = False
         stdout, stderr = None, None
         stdout, stderr = self._run_ssh(
-            [ZoneConstant.CFG_SHOW_TRANS], True, 1)
+            [zone_constant.CFG_SHOW_TRANS], True, 1)
         output = stdout.splitlines()
         is_abortable = False
         for line in output:
-            if(ZoneConstant.TRANS_ABORTABLE in line):
+            if(zone_constant.TRANS_ABORTABLE in line):
                 is_abortable = True
                 break
         if stderr:
@@ -392,6 +393,7 @@ class BrcdFCZoneClientCLI(object):
                                              None,
                                              self.switch_user,
                                              self.switch_pwd,
+                                             self.switch_key,
                                              min_size=1,
                                              max_size=5)
         last_exception = None
@@ -438,6 +440,7 @@ class BrcdFCZoneClientCLI(object):
                                              None,
                                              self.switch_user,
                                              self.switch_pwd,
+                                             self.switch_key,
                                              min_size=1,
                                              max_size=5)
         stdin, stdout, stderr = None, None, None
@@ -449,7 +452,7 @@ class BrcdFCZoneClientCLI(object):
                     attempts -= 1
                     try:
                         stdin, stdout, stderr = ssh.exec_command(command)
-                        stdin.write("%s\n" % ZoneConstant.YES)
+                        stdin.write("%s\n" % zone_constant.YES)
                         channel = stdout.channel
                         exit_status = channel.recv_exit_status()
                         LOG.debug("Exit Status from ssh: %s", exit_status)
@@ -499,7 +502,7 @@ class BrcdFCZoneClientCLI(object):
     def _execute_shell_cmd(self, cmd):
         """Run command over shell for older firmware versions.
 
-        We invoke shell and issue the command and return the output.
+        Invokes shell and issue the command and return the output.
         This is primarily used for issuing read commands when we are not sure
         if the firmware supports exec_command.
         """
@@ -512,6 +515,7 @@ class BrcdFCZoneClientCLI(object):
                                              None,
                                              self.switch_user,
                                              self.switch_pwd,
+                                             self.switch_key,
                                              min_size=1,
                                              max_size=5)
         with self.sshpool.item() as ssh:
@@ -545,6 +549,7 @@ exit
                 channel.close()
             except Exception:
                 LOG.exception(_LE('Error closing channel.'))
+            LOG.debug("_execute_cmd: stdout to return: %s", stdout)
             LOG.debug("_execute_cmd: stderr to return: %s", stderr)
         return (stdout, stderr)
 
diff --git a/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py b/cinder/zonemanager/drivers/brocade/brcd_fc_zone_connector_factory.py
new file mode 100644 (file)
index 0000000..0a4234d
--- /dev/null
@@ -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
index 93b143855eafd5aca27ec3f5a857afea91860db3..21cfbe962a279a3f56343d626f388bb817bf4a31 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2015 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2015 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -41,17 +39,18 @@ import string
 from cinder import exception
 from cinder.i18n import _, _LE, _LI, _LW
 from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as fabric_opts
+from cinder.zonemanager.drivers.brocade import fc_zone_constants
 from cinder.zonemanager.drivers import driver_utils
 from cinder.zonemanager.drivers import fc_zone_driver
+from cinder.zonemanager import utils
 
 LOG = logging.getLogger(__name__)
 
 SUPPORTED_CHARS = string.ascii_letters + string.digits + '_'
 brcd_opts = [
     cfg.StrOpt('brcd_sb_connector',
-               default='cinder.zonemanager.drivers.brocade'
-               '.brcd_fc_zone_client_cli.BrcdFCZoneClientCLI',
-               help='Southbound connector for zoning operation'),
+               default=fc_zone_constants.HTTP.upper(),
+               help='South bound connector for zoning operation'),
 ]
 
 CONF = cfg.CONF
@@ -68,9 +67,10 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
         1.0 - Initial Brocade FC zone driver
         1.1 - Implements performance enhancements
         1.2 - Added support for friendly zone name
+        1.3 - Added HTTP connector support
     """
 
-    VERSION = "1.2"
+    VERSION = "1.3"
 
     def __init__(self, **kwargs):
         super(BrcdFCZoneDriver, self).__init__(**kwargs)
@@ -103,15 +103,6 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                 self.fabric_configs = fabric_opts.load_fabric_configurations(
                     fabric_names)
 
-    def get_formatted_wwn(self, wwn_str):
-        """Utility API that formats WWN to insert ':'."""
-        wwn_str = wwn_str.encode('ascii')
-        if len(wwn_str) != 16:
-            return wwn_str
-        else:
-            return b':'.join(
-                [wwn_str[i:i + 2] for i in range(0, len(wwn_str), 2)])
-
     @lockutils.synchronized('brcd', 'fcfabric-', True)
     def add_connection(self, fabric, initiator_target_map, host_name=None,
                        storage_system=None):
@@ -147,8 +138,8 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                          "no zoning will be performed."))
             return
 
-        cli_client = self._get_cli_client(fabric)
-        cfgmap_from_fabric = self._get_active_zone_set(cli_client)
+        client = self._get_southbound_client(fabric)
+        cfgmap_from_fabric = self._get_active_zone_set(client)
 
         zone_names = []
         if cfgmap_from_fabric.get('zones'):
@@ -158,12 +149,11 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
         for initiator_key in initiator_target_map.keys():
             zone_map = {}
             initiator = initiator_key.lower()
-            t_list = initiator_target_map[initiator_key]
+            target_list = initiator_target_map[initiator_key]
             if zoning_policy == 'initiator-target':
-                for t in t_list:
-                    target = t.lower()
-                    zone_members = [self.get_formatted_wwn(initiator),
-                                    self.get_formatted_wwn(target)]
+                for target in target_list:
+                    zone_members = [utils.get_formatted_wwn(initiator),
+                                    utils.get_formatted_wwn(target)]
                     zone_name = driver_utils.get_friendly_zone_name(
                         zoning_policy,
                         initiator,
@@ -172,8 +162,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                         storage_system,
                         zone_name_prefix,
                         SUPPORTED_CHARS)
-                    if (
-                        len(cfgmap_from_fabric) == 0 or (
+                    if (len(cfgmap_from_fabric) == 0 or (
                             zone_name not in zone_names)):
                         zone_map[zone_name] = zone_members
                     else:
@@ -182,10 +171,9 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                                      "zone creation for %(zonename)s"),
                                  {'zonename': zone_name})
             elif zoning_policy == 'initiator':
-                zone_members = [self.get_formatted_wwn(initiator)]
-                for t in t_list:
-                    target = t.lower()
-                    zone_members.append(self.get_formatted_wwn(target))
+                zone_members = [utils.get_formatted_wwn(initiator)]
+                for target in target_list:
+                    zone_members.append(utils.get_formatted_wwn(target))
 
                 zone_name = driver_utils.get_friendly_zone_name(
                     zoning_policy,
@@ -208,11 +196,12 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
 
             if len(zone_map) > 0:
                 try:
-                    cli_client.add_zones(
+                    client.add_zones(
                         zone_map, zone_activate,
                         cfgmap_from_fabric)
-                    cli_client.cleanup()
-                except exception.BrocadeZoningCliException as brocade_ex:
+                    client.cleanup()
+                except (exception.BrocadeZoningCliException,
+                        exception.BrocadeZoningHttpException) as brocade_ex:
                     raise exception.FCZoneDriverException(brocade_ex)
                 except Exception:
                     msg = _("Failed to add zoning configuration.")
@@ -248,7 +237,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
             zoning_policy = zoning_policy_fab
         LOG.info(_LI("Zoning policy for fabric %(policy)s"),
                  {'policy': zoning_policy})
-        conn = self._get_cli_client(fabric)
+        conn = self._get_southbound_client(fabric)
         cfgmap_from_fabric = self._get_active_zone_set(conn)
 
         zone_names = []
@@ -262,7 +251,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                   {'cfgmap': cfgmap_from_fabric})
         for initiator_key in initiator_target_map.keys():
             initiator = initiator_key.lower()
-            formatted_initiator = self.get_formatted_wwn(initiator)
+            formatted_initiator = utils.get_formatted_wwn(initiator)
             zone_map = {}
             zones_to_delete = []
             t_list = initiator_target_map[initiator_key]
@@ -290,7 +279,7 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                 zone_members = [formatted_initiator]
                 for t in t_list:
                     target = t.lower()
-                    zone_members.append(self.get_formatted_wwn(target))
+                    zone_members.append(utils.get_formatted_wwn(target))
 
                 zone_name = driver_utils.get_friendly_zone_name(
                     zoning_policy,
@@ -353,8 +342,12 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                         zone_name_string, zone_activate,
                         cfgmap_from_fabric)
                 conn.cleanup()
+            except (exception.BrocadeZoningCliException,
+                    exception.BrocadeZoningHttpException) as brocade_ex:
+                raise exception.FCZoneDriverException(brocade_ex)
             except Exception:
-                msg = _("Failed to update or delete zoning configuration")
+                msg = _("Failed to update or delete zoning "
+                        "configuration.")
                 LOG.exception(msg)
                 raise exception.FCZoneDriverException(msg)
 
@@ -364,7 +357,6 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
         Look up each SAN configured and return a map of SAN (fabric IP) to
         list of target WWNs visible to the fabric.
         """
-        # TODO(Santhosh Kolathur): consider refactoring to use lookup service.
         formatted_target_list = []
         fabric_map = {}
         fc_fabric_names = self.configuration.fc_fabric_names
@@ -374,11 +366,11 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                   {'targetwwns': target_wwn_list})
         if len(fabrics) > 0:
             for t in target_wwn_list:
-                formatted_target_list.append(self.get_formatted_wwn(t.lower()))
+                formatted_target_list.append(utils.get_formatted_wwn(t))
             LOG.debug("Formatted target WWN list: %(targetlist)s",
                       {'targetlist': formatted_target_list})
             for fabric_name in fabrics:
-                conn = self._get_cli_client(fabric_name)
+                conn = self._get_southbound_client(fabric_name)
 
                 # Get name server data from fabric and get the targets
                 # logged in.
@@ -388,12 +380,13 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
                     LOG.debug("Name server info from fabric: %(nsinfo)s",
                               {'nsinfo': nsinfo})
                     conn.cleanup()
-                except exception.BrocadeZoningCliException:
+                except (exception.BrocadeZoningCliException,
+                        exception.BrocadeZoningHttpException):
                     if not conn.is_supported_firmware():
                         msg = _("Unsupported firmware on switch %s. Make sure "
                                 "switch is running firmware v6.4 or higher"
                                 ) % conn.switch_ip
-                        LOG.error(msg)
+                        LOG.exception(msg)
                         raise exception.FCZoneDriverException(msg)
                     with excutils.save_and_reraise_exception():
                         LOG.exception(_LE("Error getting name server info."))
@@ -425,41 +418,47 @@ class BrcdFCZoneDriver(fc_zone_driver.FCZoneDriver):
         cfgmap = None
         try:
             cfgmap = conn.get_active_zone_set()
-        except exception.BrocadeZoningCliException:
+        except (exception.BrocadeZoningCliException,
+                exception.BrocadeZoningHttpException):
             if not conn.is_supported_firmware():
                 msg = _("Unsupported firmware on switch %s. Make sure "
                         "switch is running firmware v6.4 or higher"
                         ) % conn.switch_ip
                 LOG.error(msg)
                 raise exception.FCZoneDriverException(msg)
+            with excutils.save_and_reraise_exception():
+                LOG.exception(_LE("Error getting name server info."))
         except Exception as e:
             msg = (_("Failed to retrieve active zoning configuration %s")
                    % six.text_type(e))
+            LOG.error(msg)
             raise exception.FCZoneDriverException(msg)
         LOG.debug("Active zone set from fabric: %(cfgmap)s",
                   {'cfgmap': cfgmap})
         return cfgmap
 
-    def _get_cli_client(self, fabric):
-        fabric_ip = self.fabric_configs[fabric].safe_get('fc_fabric_address')
-        fabric_user = self.fabric_configs[fabric].safe_get('fc_fabric_user')
-        fabric_pwd = self.fabric_configs[fabric].safe_get('fc_fabric_password')
-        fabric_port = self.fabric_configs[fabric].safe_get('fc_fabric_port')
-        cli_client = None
+    def _get_southbound_client(self, fabric):
+        """Implementation to get SouthBound Connector.
+
+         South bound connector will be
+         dynamically selected based on the configuration
+
+        :param fabric: fabric information
+        """
+        fabric_info = self.fabric_configs[fabric]
+        fc_ip = fabric_info.safe_get('fc_fabric_address')
+        sb_connector = fabric_info.safe_get('fc_southbound_protocol')
+        if sb_connector is None:
+            sb_connector = self.configuration.brcd_sb_connector
         try:
-            cli_client = self.sb_conn_map.get(fabric_ip)
-            if not cli_client:
-                LOG.debug("CLI client not found, creating for %(ip)s",
-                          {'ip': fabric_ip})
-                cli_client = importutils.import_object(
-                    self.configuration.brcd_sb_connector,
-                    ipaddress=fabric_ip,
-                    username=fabric_user,
-                    password=fabric_pwd,
-                    port=fabric_port)
-                self.sb_conn_map[fabric_ip] = cli_client
-        except Exception as e:
-            LOG.error(e)
-            msg = _("Failed to create sb connector for %s") % fabric_ip
+            conn_factory = importutils.import_object(
+                "cinder.zonemanager.drivers.brocade."
+                "brcd_fc_zone_connector_factory."
+                "BrcdFCZoneFactory")
+            client = conn_factory.get_connector(fabric_info,
+                                                sb_connector.upper())
+        except Exception:
+            msg = _("Failed to create south bound connector for %s.") % fc_ip
+            LOG.exception(msg)
             raise exception.FCZoneDriverException(msg)
-        return cli_client
+        return client
diff --git a/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py b/cinder/zonemanager/drivers/brocade/brcd_http_fc_zone_client.py
new file mode 100644 (file)
index 0000000..7c4398c
--- /dev/null
@@ -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()
index 1cc2372e58358acb58c98512732c413e045321e8..e9aa241fdadf57499e4f99dd7a040d5cc01bbf66 100644 (file)
@@ -1,8 +1,6 @@
 #    (c) Copyright 2014 Brocade Communications Systems Inc.
 #    All Rights Reserved.
 #
-#    Copyright 2014 OpenStack Foundation
-#
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -44,3 +42,60 @@ CFG_SHOW_TRANS = 'cfgtransshow'
 CFG_ZONE_TRANS_ABORT = 'cfgtransabort'
 NS_SHOW = 'nsshow'
 NS_CAM_SHOW = 'nscamshow'
+
+"""
+HTTPS connector constants
+"""
+AUTH_HEADER = "Authorization"
+PROTOCOL_HTTPS = "HTTPS"
+STATUS_OK = 200
+SECINFO_PAGE = "/secinfo.html"
+AUTHEN_PAGE = "/authenticate.html"
+GET_METHOD = "GET"
+POST_METHOD = "POST"
+SECINFO_BEGIN = "--BEGIN SECINFO"
+SECINFO_END = "--END SECINFO"
+RANDOM = "RANDOM"
+AUTH_STRING = "Custom_Basic "  # Trailing space is required, do not remove
+AUTHEN_BEGIN = "--BEGIN AUTHENTICATE"
+AUTHEN_END = "--END AUTHENTICATE"
+AUTHENTICATED = "authenticated"
+SESSION_PAGE_ACTION = "/session.html?action=query"
+SESSION_BEGIN = "--BEGIN SESSION"
+SESSION_END = "--END SESSION"
+SESSION_PAGE = "/session.html"
+ZONEINFO_BEGIN = "--BEGIN ZONE INFO"
+ZONEINFO_END = "--END ZONE INFO"
+SWITCH_PAGE = "/switch.html"
+SWITCHINFO_BEGIN = "--BEGIN SWITCH INFORMATION"
+SWITCHINFO_END = "--END SWITCH INFORMATION"
+FIRMWARE_VERSION = "swFWVersion"
+VF_ENABLED = "vfEnabled"
+MANAGEABLE_VF = "manageableLFList"
+CHANGE_VF = ("Session=--BEGIN SESSION\n\taction=apply\n\tLFId=  {vfid}  "
+             "\b\t--END SESSION")
+ZONE_TRAN_STATUS = "/gzoneinfo.htm?txnId={txnId}"
+CFG_DELIM = "\x01"
+ZONE_DELIM = "\x02"
+ALIAS_DELIM = "\x03"
+QLP_DELIM = "\x04"
+ZONE_END_DELIM = "\x05&saveonly="
+IFA_DELIM = "\x06"
+ACTIVE_CFG_DELIM = "\x07"
+DEFAULT_CFG = "d__efault__Cfg"
+NS_PAGE = "/nsinfo.htm"
+NSINFO_BEGIN = "--BEGIN NS INFO"
+NSINFO_END = "--END NS INFO"
+NS_DELIM = ";N    ;"
+ZONE_TX_BEGIN = "--BEGIN ZONE_TXN_INFO"
+ZONE_TX_END = "--END ZONE_TXN_INFO"
+ZONE_ERROR_CODE = "errorCode"
+ZONE_PAGE = "/gzoneinfo.htm"
+CFG_NAME = "openstack_cfg"
+ZONE_STRING_PREFIX = "zonecfginfo="
+ZONE_ERROR_MSG = "errorMessage"
+ZONE_TX_ID = "txnId"
+ZONE_TX_STATUS = "status"
+SESSION_LF_ID = "sessionLFId"
+HTTP = "http"
+HTTPS = "https"
diff --git a/releasenotes/notes/brocade_http_connector-0021e41dfa56e671.yaml b/releasenotes/notes/brocade_http_connector-0021e41dfa56e671.yaml
new file mode 100644 (file)
index 0000000..12fb938
--- /dev/null
@@ -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'.