]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Revert "Remove Cisco FC Zone Manager Driver"
authorPatrick East <patrick.east@purestorage.com>
Wed, 2 Mar 2016 03:40:10 +0000 (19:40 -0800)
committerPatrick East <patrick.east@purestorage.com>
Thu, 3 Mar 2016 05:41:04 +0000 (21:41 -0800)
This reverts commit 0f5c5d8cf7ae3623c69ca30e5dd1387f699bdc70.

As a stop gap solution Pure Storage will be running the CI for the
Cisco FCZM so that we don’t drop it for the release.

Change-Id: If257edae46ea4cd8e512e600b9182e9f50cdf546

12 files changed:
cinder/opts.py
cinder/tests/unit/zonemanager/test_cisco_fc_san_lookup_service.py [new file with mode: 0644]
cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py [new file with mode: 0644]
cinder/tests/unit/zonemanager/test_cisco_fc_zone_driver.py [new file with mode: 0644]
cinder/tests/unit/zonemanager/test_cisco_lookup_service.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/__init__.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/cisco_fabric_opts.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/cisco_fc_san_lookup_service.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py [new file with mode: 0644]
cinder/zonemanager/drivers/cisco/fc_zone_constants.py [new file with mode: 0644]
releasenotes/notes/cisco-fczm-removed-502dfcba51acd1b4.yaml [deleted file]

index d943dda22ba0039942834961f093835d673cee68..bedea755c15a40bff2668be27e69167889305062 100644 (file)
@@ -168,6 +168,10 @@ from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as \
     cinder_zonemanager_drivers_brocade_brcdfabricopts
 from cinder.zonemanager.drivers.brocade import brcd_fc_zone_driver as \
     cinder_zonemanager_drivers_brocade_brcdfczonedriver
+from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as \
+    cinder_zonemanager_drivers_cisco_ciscofabricopts
+from cinder.zonemanager.drivers.cisco import cisco_fc_zone_driver as \
+    cinder_zonemanager_drivers_cisco_ciscofczonedriver
 from cinder.zonemanager import fc_zone_manager as \
     cinder_zonemanager_fczonemanager
 
@@ -178,6 +182,7 @@ def list_opts():
             itertools.chain(
                 cinder_zonemanager_fczonemanager.zone_manager_opts,
                 cinder_zonemanager_drivers_brocade_brcdfczonedriver.brcd_opts,
+                cinder_zonemanager_drivers_cisco_ciscofczonedriver.cisco_opts,
             )),
         ('KEYMGR',
             itertools.chain(
@@ -333,6 +338,11 @@ def list_opts():
                 cinder_volume_drivers_vzstorage.vzstorage_opts,
                 cinder_volume_drivers_nfs.nfs_opts,
             )),
+        ('CISCO_FABRIC_EXAMPLE',
+            itertools.chain(
+                cinder_zonemanager_drivers_cisco_ciscofabricopts.
+                cisco_zone_opts,
+            )),
         ('BRCD_FABRIC_EXAMPLE',
             itertools.chain(
                 cinder_zonemanager_drivers_brocade_brcdfabricopts.
diff --git a/cinder/tests/unit/zonemanager/test_cisco_fc_san_lookup_service.py b/cinder/tests/unit/zonemanager/test_cisco_fc_san_lookup_service.py
new file mode 100644 (file)
index 0000000..1542853
--- /dev/null
@@ -0,0 +1,149 @@
+#    (c) Copyright 2014 Cisco 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 Cisco fc san lookup service."""
+
+import mock
+from oslo_config import cfg
+
+from cinder import exception
+from cinder import test
+from cinder.volume import configuration as conf
+import cinder.zonemanager.drivers.cisco.cisco_fc_san_lookup_service \
+    as cisco_lookup
+import cinder.zonemanager.drivers.cisco.fc_zone_constants as ZoneConstant
+from cinder.zonemanager import utils as zm_utils
+
+nsshow = '20:1a:00:05:1e:e8:e3:29'
+switch_data = ['VSAN 304\n',
+               '------------------------------------------------------\n',
+               'FCID        TYPE  PWWN                    (VENDOR)    \n',
+               '------------------------------------------------------\n',
+               '0x030001    N     20:1a:00:05:1e:e8:e3:29 (Cisco) ipfc\n',
+               '0x030101    NL    10:00:00:00:77:99:60:2c (Interphase)\n',
+               '0x030200    N     10:00:00:49:c9:28:c7:01\n']
+
+nsshow_data = ['10:00:8c:7c:ff:52:3b:01', '20:24:00:02:ac:00:0a:50']
+
+_device_map_to_verify = {
+    '304': {
+        'initiator_port_wwn_list': ['10008c7cff523b01'],
+        'target_port_wwn_list': ['20240002ac000a50']}}
+
+
+class TestCiscoFCSanLookupService(cisco_lookup.CiscoFCSanLookupService,
+                                  test.TestCase):
+
+    def setUp(self):
+        super(TestCiscoFCSanLookupService, self).setUp()
+        self.configuration = conf.Configuration(None)
+        self.configuration.set_default('fc_fabric_names', 'CISCO_FAB_2',
+                                       'fc-zone-manager')
+        self.configuration.fc_fabric_names = 'CISCO_FAB_2'
+        self.create_configuration()
+        self.fabric_vsan = '304'
+
+    # override some of the functions
+    def __init__(self, *args, **kwargs):
+        test.TestCase.__init__(self, *args, **kwargs)
+
+    def create_configuration(self):
+        fc_fabric_opts = []
+        fc_fabric_opts.append(cfg.StrOpt('cisco_fc_fabric_address',
+                                         default='172.24.173.142', help=''))
+        fc_fabric_opts.append(cfg.StrOpt('cisco_fc_fabric_user',
+                                         default='admin', help=''))
+        fc_fabric_opts.append(cfg.StrOpt('cisco_fc_fabric_password',
+                                         default='admin1234', help='',
+                                         secret=True))
+        fc_fabric_opts.append(cfg.PortOpt('cisco_fc_fabric_port',
+                                          default=22, help=''))
+        fc_fabric_opts.append(cfg.StrOpt('cisco_zoning_vsan',
+                                         default='304', help=''))
+        config = conf.Configuration(fc_fabric_opts, 'CISCO_FAB_2')
+        self.fabric_configs = {'CISCO_FAB_2': config}
+
+    @mock.patch.object(cisco_lookup.CiscoFCSanLookupService,
+                       'get_nameserver_info')
+    def test_get_device_mapping_from_network(self, get_nameserver_info_mock):
+        initiator_list = ['10008c7cff523b01']
+        target_list = ['20240002ac000a50', '20240002ac000a40']
+        get_nameserver_info_mock.return_value = (nsshow_data)
+        device_map = self.get_device_mapping_from_network(
+            initiator_list, target_list)
+        self.assertDictMatch(_device_map_to_verify, device_map)
+
+    @mock.patch.object(cisco_lookup.CiscoFCSanLookupService,
+                       '_get_switch_info')
+    def test_get_nameserver_info(self, get_switch_data_mock):
+        ns_info_list = []
+        ns_info_list_expected = ['20:1a:00:05:1e:e8:e3:29',
+                                 '10:00:00:49:c9:28:c7:01']
+        get_switch_data_mock.return_value = (switch_data)
+        ns_info_list = self.get_nameserver_info('304')
+        self.assertEqual(ns_info_list_expected, ns_info_list)
+
+    def test_parse_ns_output(self):
+        invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
+        return_wwn_list = []
+        expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29',
+                             '10:00:00:49:c9:28:c7:01']
+        return_wwn_list = self._parse_ns_output(switch_data)
+        self.assertEqual(expected_wwn_list, 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(zm_utils.get_formatted_wwn(wwn_list[0]))
+        self.assertEqual(expected_wwn_list, return_wwn_list)
+
+    @mock.patch.object(cisco_lookup.CiscoFCSanLookupService,
+                       '_run_ssh')
+    def test__get_switch_info(self, run_ssh_mock):
+        cmd_list = [ZoneConstant.FCNS_SHOW, self.fabric_vsan,
+                    ' | no-more']
+        nsshow_list = [nsshow]
+        run_ssh_mock.return_value = (Stream(nsshow), Stream())
+        switch_data = self._get_switch_info(cmd_list)
+        self.assertEqual(nsshow_list, switch_data)
+        run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
+
+
+class Channel(object):
+    def recv_exit_status(self):
+        return 0
+
+
+class Stream(object):
+    def __init__(self, buffer=''):
+        self.buffer = buffer
+        self.channel = Channel()
+
+    def readlines(self):
+        return self.buffer
+
+    def splitlines(self):
+        return self.buffer.splitlines()
+
+    def close(self):
+        pass
+
+    def flush(self):
+        self.buffer = ''
diff --git a/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py b/cinder/tests/unit/zonemanager/test_cisco_fc_zone_client_cli.py
new file mode 100644 (file)
index 0000000..b3e155e
--- /dev/null
@@ -0,0 +1,303 @@
+#    (c) Copyright 2014 Cisco 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 Cisco fc zone client cli."""
+
+import time
+
+import mock
+from oslo_concurrency import processutils
+from six.moves import range
+
+from cinder import exception
+from cinder import test
+from cinder.zonemanager.drivers.cisco \
+    import cisco_fc_zone_client_cli as cli
+import cinder.zonemanager.drivers.cisco.fc_zone_constants as ZoneConstant
+
+nsshow = '20:1a:00:05:1e:e8:e3:29'
+switch_data = ['VSAN 303\n',
+               '----------------------------------------------------------\n',
+               'FCID     TYPE  PWWN           (VENDOR)    FC4-TYPE:FEATURE\n',
+               '----------------------------------------------------------\n',
+               '0x030001 N     20:1a:00:05:1e:e8:e3:29 (Cisco)        ipfc\n',
+               '0x030101 NL    10:00:00:00:77:99:60:2c (Interphase)\n',
+               '0x030200    NL    10:00:00:49:c9:28:c7:01\n']
+
+cfgactv = ['zoneset name OpenStack_Cfg vsan 303\n',
+           'zone name openstack50060b0000c26604201900051ee8e329 vsan 303\n',
+           'pwwn 50:06:0b:00:00:c2:66:04\n',
+           'pwwn 20:19:00:05:1e:e8:e3:29\n']
+
+active_zoneset = {
+    'zones': {
+        'openstack50060b0000c26604201900051ee8e329':
+        ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']},
+    'active_zone_config': 'OpenStack_Cfg'}
+
+zoning_status_data_basic = [
+    'VSAN: 303 default-zone: deny distribute: active only Interop: default\n',
+    '    mode: basic merge-control: allow\n',
+    '    session: none\n',
+    '    hard-zoning: enabled broadcast: unsupported\n',
+    '    smart-zoning: disabled\n',
+    '    rscn-format: fabric-address\n',
+    'Default zone:\n',
+    '    qos: none broadcast: unsupported ronly: unsupported\n',
+    'Full Zoning Database :\n',
+    '    DB size: 220 bytes\n',
+    '    Zonesets:2  Zones:2 Aliases: 0\n',
+    'Active Zoning Database :\n',
+    '    DB size: 80 bytes\n',
+    '    Name: test-zs-test  Zonesets:1  Zones:1\n',
+    'Status:\n']
+
+zoning_status_basic = {'mode': 'basic', 'session': 'none'}
+
+zoning_status_data_enhanced_nosess = [
+    'VSAN: 303 default-zone: deny distribute: active only Interop: default\n',
+    '    mode: enhanced merge-control: allow\n',
+    '    session: none\n',
+    '    hard-zoning: enabled broadcast: unsupported\n',
+    '    smart-zoning: disabled\n',
+    '    rscn-format: fabric-address\n',
+    'Default zone:\n',
+    '    qos: none broadcast: unsupported ronly: unsupported\n',
+    'Full Zoning Database :\n',
+    '    DB size: 220 bytes\n',
+    '    Zonesets:2  Zones:2 Aliases: 0\n',
+    'Active Zoning Database :\n',
+    '    DB size: 80 bytes\n',
+    '    Name: test-zs-test  Zonesets:1  Zones:1\n',
+    'Status:\n']
+
+zoning_status_enhanced_nosess = {'mode': 'enhanced', 'session': 'none'}
+
+zoning_status_data_enhanced_sess = [
+    'VSAN: 303 default-zone: deny distribute: active only Interop: default\n',
+    '    mode: enhanced merge-control: allow\n',
+    '    session: otherthannone\n',
+    '    hard-zoning: enabled broadcast: unsupported\n',
+    '    smart-zoning: disabled\n',
+    '    rscn-format: fabric-address\n',
+    'Default zone:\n',
+    '    qos: none broadcast: unsupported ronly: unsupported\n',
+    'Full Zoning Database :\n',
+    '    DB size: 220 bytes\n',
+    '    Zonesets:2  Zones:2 Aliases: 0\n',
+    'Active Zoning Database :\n',
+    '    DB size: 80 bytes\n',
+    '    Name: test-zs-test  Zonesets:1  Zones:1\n',
+    'Status:\n']
+
+zoning_status_enhanced_sess = {'mode': 'enhanced', 'session': 'otherthannone'}
+
+active_zoneset_multiple_zones = {
+    'zones': {
+        'openstack50060b0000c26604201900051ee8e329':
+        ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29'],
+        'openstack10000012345678902001009876543210':
+        ['50:06:0b:00:00:c2:66:02', '20:19:00:05:1e:e8:e3:27']},
+    'active_zone_config': 'OpenStack_Cfg'}
+
+new_zone = {'openstack10000012345678902001009876543210':
+            ['10:00:00:12:34:56:78:90', '20:01:00:98:76:54:32:10']}
+
+new_zones = {'openstack10000012345678902001009876543210':
+             ['10:00:00:12:34:56:78:90', '20:01:00:98:76:54:32:10'],
+             'openstack10000011111111112001001111111111':
+             ['10:00:00:11:11:11:11:11', '20:01:00:11:11:11:11:11']}
+
+zone_names_to_delete = 'openstack50060b0000c26604201900051ee8e329'
+
+
+class TestCiscoFCZoneClientCLI(cli.CiscoFCZoneClientCLI, test.TestCase):
+
+    def setUp(self):
+        super(TestCiscoFCZoneClientCLI, self).setUp()
+        self.fabric_vsan = '303'
+
+    # override some of the functions
+    def __init__(self, *args, **kwargs):
+        test.TestCase.__init__(self, *args, **kwargs)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_get_switch_info')
+    def test_get_active_zone_set(self, get_switch_info_mock):
+        cmd_list = [ZoneConstant.GET_ACTIVE_ZONE_CFG, self.fabric_vsan,
+                    ' | no-more']
+        get_switch_info_mock.return_value = cfgactv
+        active_zoneset_returned = self.get_active_zone_set()
+        get_switch_info_mock.assert_called_once_with(cmd_list)
+        self.assertDictMatch(active_zoneset, active_zoneset_returned)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    def test_get_active_zone_set_ssh_error(self, run_ssh_mock):
+        run_ssh_mock.side_effect = processutils.ProcessExecutionError
+        self.assertRaises(exception.CiscoZoningCliException,
+                          self.get_active_zone_set)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_get_switch_info')
+    def test_get_zoning_status_basic(self, get_zoning_status_mock):
+        cmd_list = [ZoneConstant.GET_ZONE_STATUS, self.fabric_vsan]
+        get_zoning_status_mock.return_value = zoning_status_data_basic
+        zoning_status_returned = self.get_zoning_status()
+        get_zoning_status_mock.assert_called_once_with(cmd_list)
+        self.assertDictMatch(zoning_status_basic, zoning_status_returned)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_get_switch_info')
+    def test_get_zoning_status_enhanced_nosess(self, get_zoning_status_mock):
+        cmd_list = [ZoneConstant.GET_ZONE_STATUS, self.fabric_vsan]
+        get_zoning_status_mock.return_value =\
+            zoning_status_data_enhanced_nosess
+        zoning_status_returned = self.get_zoning_status()
+        get_zoning_status_mock.assert_called_once_with(cmd_list)
+        self.assertDictMatch(zoning_status_enhanced_nosess,
+                             zoning_status_returned)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_get_switch_info')
+    def test_get_zoning_status_enhanced_sess(self, get_zoning_status_mock):
+        cmd_list = [ZoneConstant.GET_ZONE_STATUS, self.fabric_vsan]
+        get_zoning_status_mock.return_value = zoning_status_data_enhanced_sess
+        zoning_status_returned = self.get_zoning_status()
+        get_zoning_status_mock.assert_called_once_with(cmd_list)
+        self.assertDictMatch(zoning_status_enhanced_sess,
+                             zoning_status_returned)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_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()
+        self.assertEqual(ns_info_list_expected, ns_info_list)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    def test_get_nameserver_info_ssh_error(self, run_ssh_mock):
+        run_ssh_mock.side_effect = processutils.ProcessExecutionError
+        self.assertRaises(exception.CiscoZoningCliException,
+                          self.get_nameserver_info)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    def test__cfg_save(self, run_ssh_mock):
+        cmd_list = ['copy', 'running-config', 'startup-config']
+        self._cfg_save()
+        run_ssh_mock.assert_called_once_with(cmd_list, True)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    @mock.patch.object(time, 'sleep')
+    def test__cfg_save_with_retry(self, mock_sleep, run_ssh_mock):
+        cmd_list = ['copy', 'running-config', 'startup-config']
+        run_ssh_mock.side_effect = [
+            processutils.ProcessExecutionError,
+            ('', None)
+        ]
+
+        self._cfg_save()
+
+        self.assertEqual(2, run_ssh_mock.call_count)
+        run_ssh_mock.assert_has_calls([
+            mock.call(cmd_list, True),
+            mock.call(cmd_list, True)
+        ])
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    @mock.patch.object(time, 'sleep')
+    def test__cfg_save_with_error(self, mock_sleep, run_ssh_mock):
+        cmd_list = ['copy', 'running-config', 'startup-config']
+        run_ssh_mock.side_effect = processutils.ProcessExecutionError
+
+        self.assertRaises(processutils.ProcessExecutionError, self._cfg_save)
+
+        expected_num_calls = 5
+        expected_calls = []
+        for i in range(expected_num_calls):
+            expected_calls.append(mock.call(cmd_list, True))
+
+        self.assertEqual(expected_num_calls, run_ssh_mock.call_count)
+        run_ssh_mock.assert_has_calls(expected_calls)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_run_ssh')
+    def test__get_switch_info(self, run_ssh_mock):
+        cmd_list = [ZoneConstant.FCNS_SHOW, self.fabric_vsan]
+        nsshow_list = [nsshow]
+        run_ssh_mock.return_value = (Stream(nsshow), Stream())
+        switch_data = self._get_switch_info(cmd_list)
+        self.assertEqual(nsshow_list, switch_data)
+        run_ssh_mock.assert_called_once_with(cmd_list, True)
+
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_ssh_execute')
+    @mock.patch.object(cli.CiscoFCZoneClientCLI, '_cfg_save')
+    def test__add_zones_with_update(self, ssh_execute_mock, cfg_save_mock):
+        self.add_zones(new_zone, False, self.fabric_vsan,
+                       active_zoneset_multiple_zones,
+                       zoning_status_basic)
+        self.assertEqual(2, ssh_execute_mock.call_count)
+        self.assertEqual(2, cfg_save_mock.call_count)
+
+    def test__parse_ns_output(self):
+        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)
+
+
+class TestCiscoFCZoneClientCLISSH(test.TestCase):
+
+    def setUp(self):
+        super(TestCiscoFCZoneClientCLISSH, self).setUp()
+        self.client = cli.CiscoFCZoneClientCLI(None, None, None, None, None)
+        self.client.sshpool = mock.MagicMock()
+        self.mock_ssh = self.client.sshpool.item().__enter__()
+
+    @mock.patch('oslo_concurrency.processutils.ssh_execute')
+    def test__run_ssh(self, mock_execute):
+        mock_execute.return_value = 'ssh output'
+        ret = self.client._run_ssh(['cat', 'foo'])
+        self.assertEqual('ssh output', ret)
+        mock_execute.assert_called_once_with(self.mock_ssh,
+                                             'cat foo',
+                                             check_exit_code=True)
+
+    @mock.patch('oslo_concurrency.processutils.ssh_execute')
+    def test__run_ssh_with_error(self, mock_execute):
+        mock_execute.side_effect = processutils.ProcessExecutionError()
+        self.assertRaises(processutils.ProcessExecutionError,
+                          self.client._run_ssh,
+                          ['cat', 'foo'])
+
+
+class Channel(object):
+    def recv_exit_status(self):
+        return 0
+
+
+class Stream(object):
+    def __init__(self, buffer=''):
+        self.buffer = buffer
+        self.channel = Channel()
+
+    def readlines(self):
+        return self.buffer
+
+    def splitlines(self):
+        return self.buffer.splitlines()
+
+    def close(self):
+        pass
+
+    def flush(self):
+        self.buffer = ''
diff --git a/cinder/tests/unit/zonemanager/test_cisco_fc_zone_driver.py b/cinder/tests/unit/zonemanager/test_cisco_fc_zone_driver.py
new file mode 100644 (file)
index 0000000..42e5c55
--- /dev/null
@@ -0,0 +1,208 @@
+#    (c) Copyright 2014 Cisco 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 Cisco FC zone driver."""
+
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_utils import importutils
+
+from cinder import exception
+from cinder import test
+from cinder.volume import configuration as conf
+
+_active_cfg_before_add = {}
+_active_cfg_before_delete = {
+    'zones': {
+        'openstack10008c7cff523b0120240002ac000a50': (
+            ['10:00:8c:7c:ff:52:3b:01',
+             '20:24:00:02:ac:00:0a:50'])},
+        'active_zone_config': 'cfg1'}
+_activate = True
+_zone_name = 'openstack10008c7cff523b0120240002ac000a50'
+_target_ns_map = {'100000051e55a100': ['20240002ac000a50']}
+_zoning_status = {'mode': 'basis', 'session': 'none'}
+_initiator_ns_map = {'100000051e55a100': ['10008c7cff523b01']}
+_zone_map_to_add = {'openstack10008c7cff523b0120240002ac000a50': (
+    ['10:00:8c:7c:ff:52:3b:01', '20:24:00:02:ac:00:0a:50'])}
+
+_initiator_target_map = {'10008c7cff523b01': ['20240002ac000a50']}
+_device_map_to_verify = {
+    '304': {
+        'initiator_port_wwn_list': [
+            '10008c7cff523b01'], 'target_port_wwn_list': ['20240002ac000a50']}}
+_fabric_wwn = '304'
+
+
+class CiscoFcZoneDriverBaseTest(object):
+
+    def setup_config(self, is_normal, mode):
+        fc_test_opts = [
+            cfg.StrOpt('fc_fabric_address_CISCO_FAB_1', default='10.24.48.213',
+                       help='FC Fabric names'),
+        ]
+        configuration = conf.Configuration(fc_test_opts)
+        # fill up config
+        configuration.zoning_mode = 'fabric'
+        configuration.zone_driver = ('cinder.tests.unit.zonemanager.'
+                                     'test_cisco_fc_zone_driver.'
+                                     'FakeCiscoFCZoneDriver')
+        configuration.cisco_sb_connector = ('cinder.tests.unit.zonemanager.'
+                                            'test_cisco_fc_zone_driver'
+                                            '.FakeCiscoFCZoneClientCLI')
+        configuration.zoning_policy = 'initiator-target'
+        configuration.zone_activate = True
+        configuration.zone_name_prefix = 'openstack'
+        configuration.fc_san_lookup_service = ('cinder.tests.unit.zonemanager.'
+                                               'test_cisco_fc_zone_driver.'
+                                               'FakeCiscoFCSanLookupService')
+
+        configuration.fc_fabric_names = 'CISCO_FAB_1'
+        configuration.fc_fabric_address_CISCO_FAB_1 = '172.21.60.220'
+        if (is_normal):
+            configuration.fc_fabric_user_CISCO_FAB_1 = 'admin'
+        else:
+            configuration.fc_fabric_user_CISCO_FAB_1 = 'invaliduser'
+        configuration.fc_fabric_password_CISCO_FAB_1 = 'admin1234'
+
+        if (mode == 1):
+            configuration.zoning_policy_CISCO_FAB_1 = 'initiator-target'
+        elif (mode == 2):
+            configuration.zoning_policy_CISCO_FAB_1 = 'initiator'
+        else:
+            configuration.zoning_policy_CISCO_FAB_1 = 'initiator-target'
+        configuration.zone_activate_CISCO_FAB_1 = True
+        configuration.zone_name_prefix_CISCO_FAB_1 = 'openstack'
+        configuration.zoning_vsan_CISCO_FAB_1 = '304'
+        return configuration
+
+
+class TestCiscoFcZoneDriver(CiscoFcZoneDriverBaseTest, test.TestCase):
+
+    def setUp(self):
+        super(TestCiscoFcZoneDriver, self).setUp()
+        # setup config for normal flow
+        self.setup_driver(self.setup_config(True, 1))
+        GlobalVars._zone_state = []
+
+    def setup_driver(self, config):
+        self.driver = importutils.import_object(
+            'cinder.zonemanager.drivers.cisco.cisco_fc_zone_driver'
+            '.CiscoFCZoneDriver', configuration=config)
+
+    def fake_get_active_zone_set(self, fabric_ip, fabric_user, fabric_pwd,
+                                 zoning_vsan):
+        return GlobalVars._active_cfg
+
+    def fake_get_san_context(self, target_wwn_list):
+        fabric_map = {}
+        return fabric_map
+
+    def test_delete_connection(self):
+        GlobalVars._is_normal_test = True
+        GlobalVars._active_cfg = _active_cfg_before_delete
+        self.driver.delete_connection(
+            'CISCO_FAB_1', _initiator_target_map)
+        self.assertFalse(_zone_name in GlobalVars._zone_state)
+
+    def test_delete_connection_for_initiator_mode(self):
+        GlobalVars._is_normal_test = True
+        GlobalVars._active_cfg = _active_cfg_before_delete
+        self.setup_driver(self.setup_config(True, 2))
+        self.driver.delete_connection(
+            'CISCO_FAB_1', _initiator_target_map)
+        self.assertFalse(_zone_name in GlobalVars._zone_state)
+
+    def test_add_connection_for_invalid_fabric(self):
+        """Test abnormal flows."""
+        GlobalVars._is_normal_test = True
+        GlobalVars._active_cfg = _active_cfg_before_add
+        GlobalVars._is_normal_test = False
+        self.setup_driver(self.setup_config(False, 1))
+        self.assertRaises(exception.FCZoneDriverException,
+                          self.driver.add_connection,
+                          'CISCO_FAB_1',
+                          _initiator_target_map)
+
+    def test_delete_connection_for_invalid_fabric(self):
+        GlobalVars._active_cfg = _active_cfg_before_delete
+        GlobalVars._is_normal_test = False
+        self.setup_driver(self.setup_config(False, 1))
+        self.assertRaises(exception.FCZoneDriverException,
+                          self.driver.delete_connection,
+                          'CISCO_FAB_1',
+                          _initiator_target_map)
+
+
+class FakeCiscoFCZoneClientCLI(object):
+    def __init__(self, ipaddress, username, password, port, vsan):
+        if not GlobalVars._is_normal_test:
+            raise processutils.ProcessExecutionError(
+                "Unable to connect to fabric")
+
+    def get_active_zone_set(self):
+        return GlobalVars._active_cfg
+
+    def add_zones(self, zones, isActivate):
+        GlobalVars._zone_state.extend(zones.keys())
+
+    def delete_zones(self, zone_names, isActivate):
+        zone_list = zone_names.split(';')
+        GlobalVars._zone_state = [
+            x for x in GlobalVars._zone_state if x not in zone_list]
+
+    def get_nameserver_info(self):
+        return _target_ns_map
+
+    def get_zoning_status(self):
+        return _zoning_status
+
+    def close_connection(self):
+        pass
+
+    def cleanup(self):
+        pass
+
+
+class FakeCiscoFCSanLookupService(object):
+    def get_device_mapping_from_network(self,
+                                        initiator_wwn_list,
+                                        target_wwn_list):
+        device_map = {}
+        initiators = []
+        targets = []
+        for i in initiator_wwn_list:
+            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]):
+                targets.append(t)
+        device_map[_fabric_wwn] = {
+            'initiator_port_wwn_list': initiators,
+            'target_port_wwn_list': targets}
+        return device_map
+
+
+class GlobalVars(object):
+    global _active_cfg
+    _active_cfg = {}
+    global _zone_state
+    _zone_state = list()
+    global _is_normal_test
+    _is_normal_test = True
+    global _zoning_status
+    _zoning_status = {}
diff --git a/cinder/tests/unit/zonemanager/test_cisco_lookup_service.py b/cinder/tests/unit/zonemanager/test_cisco_lookup_service.py
new file mode 100644 (file)
index 0000000..678fd83
--- /dev/null
@@ -0,0 +1,96 @@
+#    (c) Copyright 2014 Cisco 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 Cisco FC san lookup service."""
+
+from cinder import exception
+from cinder import test
+from cinder.volume import configuration as conf
+from cinder.zonemanager import fc_san_lookup_service as san_service
+
+_target_ns_map = {'100000051e55a100': ['20240002ac000a50']}
+_initiator_ns_map = {'100000051e55a100': ['10008c7cff523b01']}
+_device_map_to_verify = {
+    '100000051e55a100': {
+        'initiator_port_wwn_list': [
+            '10008c7cff523b01'], 'target_port_wwn_list': ['20240002ac000a50']}}
+_fabric_wwn = '100000051e55a100'
+
+
+class TestFCSanLookupService(san_service.FCSanLookupService, test.TestCase):
+
+    def setUp(self):
+        super(TestFCSanLookupService, self).setUp()
+        self.configuration = self.setup_config()
+
+    # override some of the functions
+    def __init__(self, *args, **kwargs):
+        test.TestCase.__init__(self, *args, **kwargs)
+
+    def setup_config(self):
+        configuration = conf.Configuration(None)
+        # fill up config
+        configuration.fc_san_lookup_service = ('cinder.tests.unit.zonemanager'
+                                               '.test_cisco_lookup_service'
+                                               '.FakeCiscoFCSanLookupService')
+        return configuration
+
+    def test_get_device_mapping_from_network(self):
+        GlobalParams._is_normal_test = True
+        initiator_list = ['10008c7cff523b01']
+        target_list = ['20240002ac000a50', '20240002ac000a40']
+        device_map = self.get_device_mapping_from_network(
+            initiator_list, target_list)
+        self.assertDictMatch(_device_map_to_verify, device_map)
+
+    def test_get_device_mapping_from_network_for_invalid_config(self):
+        GlobalParams._is_normal_test = False
+        initiator_list = ['10008c7cff523b01']
+        target_list = ['20240002ac000a50', '20240002ac000a40']
+        self.assertRaises(exception.FCSanLookupServiceException,
+                          self.get_device_mapping_from_network,
+                          initiator_list, target_list)
+
+
+class FakeCiscoFCSanLookupService(object):
+
+    def __init__(self, **kwargs):
+        pass
+
+    def get_device_mapping_from_network(self,
+                                        initiator_wwn_list,
+                                        target_wwn_list):
+        if not GlobalParams._is_normal_test:
+            raise exception.FCSanLookupServiceException("Error")
+        device_map = {}
+        initiators = []
+        targets = []
+        for i in initiator_wwn_list:
+            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]):
+                targets.append(t)
+        device_map[_fabric_wwn] = {
+            'initiator_port_wwn_list': initiators,
+            'target_port_wwn_list': targets}
+        return device_map
+
+
+class GlobalParams(object):
+    global _is_normal_test
+    _is_normal_test = True
diff --git a/cinder/zonemanager/drivers/cisco/__init__.py b/cinder/zonemanager/drivers/cisco/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/zonemanager/drivers/cisco/cisco_fabric_opts.py b/cinder/zonemanager/drivers/cisco/cisco_fabric_opts.py
new file mode 100644 (file)
index 0000000..524dcc1
--- /dev/null
@@ -0,0 +1,56 @@
+#    (c) Copyright 2014 Cisco 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.
+#
+from oslo_config import cfg
+
+from cinder.volume import configuration
+
+cisco_zone_opts = [
+    cfg.StrOpt('cisco_fc_fabric_address',
+               default='',
+               help='Management IP of fabric'),
+    cfg.StrOpt('cisco_fc_fabric_user',
+               default='',
+               help='Fabric user ID'),
+    cfg.StrOpt('cisco_fc_fabric_password',
+               default='',
+               help='Password for user',
+               secret=True),
+    cfg.PortOpt('cisco_fc_fabric_port',
+                default=22,
+                help='Connecting port'),
+    cfg.StrOpt('cisco_zoning_policy',
+               default='initiator-target',
+               help='overridden zoning policy'),
+    cfg.BoolOpt('cisco_zone_activate',
+                default=True,
+                help='overridden zoning activation state'),
+    cfg.StrOpt('cisco_zone_name_prefix',
+               help='overridden zone name prefix'),
+    cfg.StrOpt('cisco_zoning_vsan',
+               help='VSAN of the Fabric'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(cisco_zone_opts, group='CISCO_FABRIC_EXAMPLE')
+
+
+def load_fabric_configurations(fabric_names):
+    fabric_configs = {}
+    for fabric_name in fabric_names:
+        config = configuration.Configuration(cisco_zone_opts, fabric_name)
+        fabric_configs[fabric_name] = config
+
+    return fabric_configs
diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_san_lookup_service.py b/cinder/zonemanager/drivers/cisco/cisco_fc_san_lookup_service.py
new file mode 100644 (file)
index 0000000..77aaaa9
--- /dev/null
@@ -0,0 +1,355 @@
+#    (c) Copyright 2014 Cisco 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.
+#
+
+
+import random
+
+from eventlet import greenthread
+from oslo_concurrency import processutils
+from oslo_log import log as logging
+from oslo_utils import excutils
+import six
+
+from cinder import exception
+from cinder.i18n import _, _LE
+from cinder import ssh_utils
+from cinder import utils
+from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as fabric_opts
+import cinder.zonemanager.drivers.cisco.fc_zone_constants as zone_constant
+from cinder.zonemanager import fc_san_lookup_service as fc_service
+from cinder.zonemanager import utils as zm_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class CiscoFCSanLookupService(fc_service.FCSanLookupService):
+    """The SAN lookup service that talks to Cisco switches.
+
+    Version History:
+        1.0.0 - Initial version
+
+    """
+
+    VERSION = "1.0.0"
+
+    def __init__(self, **kwargs):
+        """Initializing the client."""
+        super(CiscoFCSanLookupService, self).__init__(**kwargs)
+        self.configuration = kwargs.get('configuration', None)
+        self.create_configuration()
+
+        self.switch_user = ""
+        self.switch_port = ""
+        self.switch_pwd = ""
+        self.switch_ip = ""
+        self.sshpool = None
+
+    def create_configuration(self):
+        """Configuration specific to SAN context values."""
+        config = self.configuration
+
+        fabric_names = [x.strip() for x in config.fc_fabric_names.split(',')]
+        LOG.debug('Fabric Names: %s', fabric_names)
+
+        # There can be more than one SAN in the network and we need to
+        # get credentials for each for SAN context lookup later.
+        # Cisco Zonesets require VSANs
+        if fabric_names:
+            self.fabric_configs = fabric_opts.load_fabric_configurations(
+                fabric_names)
+
+    def get_device_mapping_from_network(self,
+                                        initiator_wwn_list,
+                                        target_wwn_list):
+        """Provides the initiator/target map for available SAN contexts.
+
+        Looks up fcns database of each fc SAN configured to find logged in
+        devices and returns a map of initiator and target port WWNs for each
+        fabric.
+
+        :param initiator_wwn_list: List of initiator port WWN
+        :param target_wwn_list: List of target port WWN
+        :returns: List -- device wwn map in following format
+            {
+                <San name>: {
+                    'initiator_port_wwn_list':
+                    ('200000051e55a100', '200000051e55a121'..)
+                    'target_port_wwn_list':
+                    ('100000051e55a100', '100000051e55a121'..)
+                }
+            }
+        :raises: Exception when connection to fabric is failed
+        """
+        device_map = {}
+        formatted_target_list = []
+        formatted_initiator_list = []
+        fabric_map = {}
+        fabric_names = self.configuration.fc_fabric_names
+
+        if not fabric_names:
+            raise exception.InvalidParameterValue(
+                err=_("Missing Fibre Channel SAN configuration "
+                      "param - fc_fabric_names"))
+
+        fabrics = [x.strip() for x in fabric_names.split(',')]
+
+        LOG.debug("FC Fabric List: %s", fabrics)
+        if fabrics:
+            for t in target_wwn_list:
+                formatted_target_list.append(zm_utils.get_formatted_wwn(t))
+
+            for i in initiator_wwn_list:
+                formatted_initiator_list.append(zm_utils.get_formatted_wwn(i))
+
+            for fabric_name in fabrics:
+                self.switch_ip = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_address')
+                self.switch_user = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_user')
+                self.switch_pwd = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_password')
+                self.switch_port = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_port')
+                zoning_vsan = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_zoning_vsan')
+
+                # Get name server data from fabric and find the targets
+                # logged in
+                nsinfo = ''
+                LOG.debug("show fcns database for vsan %s", zoning_vsan)
+                nsinfo = self.get_nameserver_info(zoning_vsan)
+
+                LOG.debug("Lookup service:fcnsdatabase-%s", nsinfo)
+                LOG.debug("Lookup service:initiator list from caller-%s",
+                          formatted_initiator_list)
+                LOG.debug("Lookup service:target list from caller-%s",
+                          formatted_target_list)
+                visible_targets = [x for x in nsinfo
+                                   if x in formatted_target_list]
+                visible_initiators = [x for x in nsinfo
+                                      if x in formatted_initiator_list]
+
+                if visible_targets:
+                    LOG.debug("Filtered targets is: %s", visible_targets)
+                    # getting rid of the : before returning
+                    for idx, elem in enumerate(visible_targets):
+                        elem = str(elem).replace(':', '')
+                        visible_targets[idx] = elem
+                else:
+                    LOG.debug("No targets are in the fcns database"
+                              " for vsan %s", zoning_vsan)
+
+                if visible_initiators:
+                    # getting rid of the : before returning ~sk
+                    for idx, elem in enumerate(visible_initiators):
+                        elem = str(elem).replace(':', '')
+                        visible_initiators[idx] = elem
+                else:
+                    LOG.debug("No initiators are in the fcns database"
+                              " for vsan %s", zoning_vsan)
+
+                fabric_map = {'initiator_port_wwn_list': visible_initiators,
+                              'target_port_wwn_list': visible_targets
+                              }
+                device_map[zoning_vsan] = fabric_map
+        LOG.debug("Device map for SAN context: %s", device_map)
+        return device_map
+
+    def get_nameserver_info(self, fabric_vsan):
+        """Get fcns database info from fabric.
+
+        This method will return the connected node port wwn list(local
+        and remote) for the given switch fabric
+        """
+        cli_output = None
+        nsinfo_list = []
+        try:
+            cmd = ([zone_constant.FCNS_SHOW, fabric_vsan, ' | no-more'])
+            cli_output = self._get_switch_info(cmd)
+        except exception.FCSanLookupServiceException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed collecting show fcns database for"
+                              " fabric"))
+        if cli_output:
+            nsinfo_list = self._parse_ns_output(cli_output)
+
+        LOG.debug("Connector returning fcns info-%s", nsinfo_list)
+        return nsinfo_list
+
+    def _get_switch_info(self, cmd_list):
+        stdout, stderr, sw_data = None, None, None
+        try:
+            stdout, stderr = self._run_ssh(cmd_list, True, 1)
+            LOG.debug("CLI output from ssh - output: %s", stdout)
+            if (stdout):
+                sw_data = stdout.splitlines()
+            return sw_data
+        except processutils.ProcessExecutionError as e:
+            msg = _("Error while getting data via ssh: (command=%(cmd)s "
+                    "error=%(err)s).") % {'cmd': cmd_list,
+                                          'err': six.text_type(e)}
+            LOG.error(msg)
+            raise exception.CiscoZoningCliException(reason=msg)
+
+    def _parse_ns_output(self, switch_data):
+        """Parses name server data.
+
+        Parses nameserver raw data and adds the device port wwns to the list
+
+        :returns: list of device port wwn from ns info
+        """
+        nsinfo_list = []
+        for line in switch_data:
+            if not(" N " in line):
+                continue
+            linesplit = line.split()
+            if len(linesplit) > 2:
+                node_port_wwn = linesplit[2]
+                nsinfo_list.append(node_port_wwn)
+            else:
+                msg = _("Malformed fcns output string: %s") % line
+                LOG.error(msg)
+                raise exception.InvalidParameterValue(err=msg)
+        return nsinfo_list
+
+    def _run_ssh(self, cmd_list, check_exit_code=True, attempts=1):
+
+        command = ' '.join(cmd_list)
+
+        if not self.sshpool:
+            self.sshpool = ssh_utils.SSHPool(self.switch_ip,
+                                             self.switch_port,
+                                             None,
+                                             self.switch_user,
+                                             self.switch_pwd,
+                                             min_size=1,
+                                             max_size=5)
+        last_exception = None
+        try:
+            with self.sshpool.item() as ssh:
+                while attempts > 0:
+                    attempts -= 1
+                    try:
+                        return processutils.ssh_execute(
+                            ssh,
+                            command,
+                            check_exit_code=check_exit_code)
+                    except Exception as e:
+                        msg = _("Exception: %s") % six.text_type(e)
+                        LOG.error(msg)
+                        last_exception = e
+                        greenthread.sleep(random.randint(20, 500) / 100.0)
+                try:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=last_exception.exit_code,
+                        stdout=last_exception.stdout,
+                        stderr=last_exception.stderr,
+                        cmd=last_exception.cmd)
+                except AttributeError:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=-1,
+                        stdout="",
+                        stderr="Error running SSH command",
+                        cmd=command)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Error running SSH command: %s"), command)
+
+    def _ssh_execute(self, cmd_list, check_exit_code=True, attempts=1):
+        """Execute cli with status update.
+
+        Executes CLI commands where status return is expected.
+
+        cmd_list is a list of commands, where each command is itself
+        a list of parameters.  We use utils.check_ssh_injection to check each
+        command, but then join then with " ; " to form a single command.
+        """
+
+        # Check that each command is secure
+        for cmd in cmd_list:
+            utils.check_ssh_injection(cmd)
+
+        # Combine into a single command.
+        command = ' ; '.join(map(lambda x: ' '.join(x), cmd_list))
+
+        if not self.sshpool:
+            self.sshpool = ssh_utils.SSHPool(self.switch_ip,
+                                             self.switch_port,
+                                             None,
+                                             self.switch_user,
+                                             self.switch_pwd,
+                                             min_size=1,
+                                             max_size=5)
+        stdin, stdout, stderr = None, None, None
+        LOG.debug("Executing command via ssh: %s", command)
+        last_exception = None
+        try:
+            with self.sshpool.item() as ssh:
+                while attempts > 0:
+                    attempts -= 1
+                    try:
+                        stdin, stdout, stderr = ssh.exec_command(command)
+                        greenthread.sleep(random.randint(20, 500) / 100.0)
+                        channel = stdout.channel
+                        exit_status = channel.recv_exit_status()
+                        LOG.debug("Exit Status from ssh:%s", exit_status)
+                        # exit_status == -1 if no exit code was returned
+                        if exit_status != -1:
+                            LOG.debug('Result was %s', exit_status)
+                            if check_exit_code and exit_status != 0:
+                                raise processutils.ProcessExecutionError(
+                                    exit_code=exit_status,
+                                    stdout=stdout,
+                                    stderr=stderr,
+                                    cmd=command)
+                            else:
+                                return True
+                        else:
+                            return True
+                    except Exception as e:
+                        msg = _("Exception: %s") % six.text_type(e)
+                        LOG.error(msg)
+                        last_exception = e
+                        greenthread.sleep(random.randint(20, 500) / 100.0)
+                LOG.debug("Handling error case after SSH:%s", last_exception)
+                try:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=last_exception.exit_code,
+                        stdout=last_exception.stdout,
+                        stderr=last_exception.stderr,
+                        cmd=last_exception.cmd)
+                except AttributeError:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=-1,
+                        stdout="",
+                        stderr="Error running SSH command",
+                        cmd=command)
+        except Exception as e:
+            with excutils.save_and_reraise_exception():
+                msg = (_("Error executing command via ssh: %s") %
+                       six.text_type(e))
+                LOG.error(msg)
+        finally:
+            if stdin:
+                stdin.flush()
+                stdin.close()
+            if stdout:
+                stdout.close()
+            if stderr:
+                stderr.close()
+
+    def cleanup(self):
+        self.sshpool = None
diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_client_cli.py
new file mode 100644 (file)
index 0000000..924dbe3
--- /dev/null
@@ -0,0 +1,460 @@
+#    (c) Copyright 2014 Cisco 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.
+#
+
+
+"""
+Script to push the zone configuration to Cisco SAN switches.
+"""
+import random
+import re
+
+from eventlet import greenthread
+from oslo_concurrency import processutils
+from oslo_log import log as logging
+from oslo_utils import excutils
+import six
+
+from cinder import exception
+from cinder.i18n import _, _LE, _LI, _LW
+from cinder import ssh_utils
+from cinder import utils
+import cinder.zonemanager.drivers.cisco.fc_zone_constants as ZoneConstant
+
+LOG = logging.getLogger(__name__)
+
+
+class CiscoFCZoneClientCLI(object):
+    """Cisco FC zone client cli implementation.
+
+    OpenStack Fibre Channel zone client cli connector
+    to manage FC zoning in Cisco SAN fabrics.
+
+    Version history:
+        1.0 - Initial Cisco FC zone client cli
+    """
+
+    switch_ip = None
+    switch_port = '22'
+    switch_user = 'admin'
+    switch_pwd = 'none'
+
+    def __init__(self, ipaddress, username, password, port, vsan):
+        """initializing the client."""
+        self.switch_ip = ipaddress
+        self.switch_port = port
+        self.switch_user = username
+        self.switch_pwd = password
+        self.fabric_vsan = vsan
+        self.sshpool = None
+
+    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'
+        }
+        """
+        zone_set = {}
+        zone = {}
+        zone_member = None
+        zone_name = None
+        switch_data = None
+        zone_set_name = None
+        try:
+            switch_data = self._get_switch_info(
+                [ZoneConstant.GET_ACTIVE_ZONE_CFG, self.fabric_vsan,
+                 ' | no-more'])
+        except exception.CiscoZoningCliException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed getting active zone set "
+                              "from fabric %s"), self.switch_ip)
+        try:
+            for line in switch_data:
+                # Split on non-word characters,
+                line_split = re.split('[\s\[\]]+', line)
+                if ZoneConstant.CFG_ZONESET in line_split:
+                    # zoneset name [name] vsan [vsan]
+                    zone_set_name = \
+                        line_split[line_split.index(ZoneConstant.CFG_ZONESET)
+                                   + 2]
+                    continue
+                if ZoneConstant.CFG_ZONE in line_split:
+                    # zone name [name] vsan [vsan]
+                    zone_name = \
+                        line_split[line_split.index(ZoneConstant.CFG_ZONE) + 2]
+                    zone[zone_name] = list()
+                    continue
+                if ZoneConstant.CFG_ZONE_MEMBER in line_split:
+                    # Examples:
+                    # pwwn c0:50:76:05:15:9f:00:12
+                    # * fcid 0x1e01c0 [pwwn 50:05:07:68:02:20:48:04] [V7K_N1P2]
+                    zone_member = \
+                        line_split[
+                            line_split.index(ZoneConstant.CFG_ZONE_MEMBER) + 1]
+                    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
+        except Exception as ex:
+            # 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,
+                         'zone_config': switch_data}
+            LOG.error(msg)
+            exc_msg = _("Exception: %s") % six.text_type(ex)
+            LOG.error(exc_msg)
+            raise exception.FCZoneDriverException(reason=msg)
+
+        return zone_set
+
+    def add_zones(self, zones, activate, fabric_vsan, active_zone_set,
+                  zone_status):
+        """Add zone configuration.
+
+        This method will add the zone configuration passed by user.
+            input params:
+            zones - zone names mapped to members and VSANs.
+            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']
+                }
+            activate - True/False
+        """
+        LOG.debug("Add Zones - Zones passed: %s", zones)
+
+        LOG.debug("Active zone set: %s", active_zone_set)
+        zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+        LOG.debug("zone list: %s", zone_list)
+        LOG.debug("zone status: %s", zone_status)
+
+        cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
+
+        zone_cmds = [['conf'],
+                     ['zoneset', 'name', cfg_name, 'vsan', fabric_vsan]]
+
+        for zone in zones.keys():
+            # if zone exists, its an update. Delete & insert
+            LOG.debug("Update call")
+            if zone in zone_list:
+                # Response from get_active_zone_set strips colons from WWPNs
+                current_zone = set(zone_list[zone])
+                new_wwpns = map(lambda x: x.lower().replace(':', ''),
+                                zones[zone])
+                new_zone = set(new_wwpns)
+
+                if current_zone != new_zone:
+                    try:
+                        self.delete_zones(zone, activate, fabric_vsan,
+                                          active_zone_set, zone_status)
+                    except exception.CiscoZoningCliException:
+                        with excutils.save_and_reraise_exception():
+                            LOG.error(_LE("Deleting zone failed %s"), zone)
+                    LOG.debug("Deleted Zone before insert : %s", zone)
+
+            zone_cmds.append(['zone', 'name', zone])
+
+            for member in zones[zone]:
+                zone_cmds.append(['member', 'pwwn', member])
+
+        zone_cmds.append(['end'])
+
+        try:
+            LOG.debug("Add zones: Config cmd to run: %s", zone_cmds)
+            self._ssh_execute(zone_cmds, True, 1)
+
+            if activate:
+                self.activate_zoneset(cfg_name, fabric_vsan, zone_status)
+            self._cfg_save()
+        except Exception as e:
+
+            msg = _("Creating and activating zone set failed: "
+                    "(Zone set=%(zoneset)s error=%(err)s)."
+                    ) % {'zoneset': cfg_name, 'err': six.text_type(e)}
+            LOG.error(msg)
+            raise exception.CiscoZoningCliException(reason=msg)
+
+    def activate_zoneset(self, cfgname, fabric_vsan, zone_status):
+        """Method to Activate the zone config. Param cfgname - ZonesetName."""
+
+        LOG.debug("zone status: %s", zone_status)
+
+        cmd_list = [['conf'],
+                    ['zoneset', 'activate', 'name', cfgname, 'vsan',
+                     self.fabric_vsan]]
+        if zone_status['mode'] == 'enhanced':
+            cmd_list.append(['zone', 'commit', 'vsan', fabric_vsan])
+
+        cmd_list.append(['end'])
+
+        return self._ssh_execute(cmd_list, True, 1)
+
+    def get_zoning_status(self):
+        """Return the zoning mode and session for a zoneset."""
+        zone_status = {}
+
+        try:
+            switch_data = self._get_switch_info(
+                [ZoneConstant.GET_ZONE_STATUS, self.fabric_vsan])
+        except exception.CiscoZoningCliException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed getting zone status "
+                              "from fabric %s"), self.switch_ip)
+        try:
+            for line in switch_data:
+                # Split on non-word characters,
+                line_split = re.split('[\s\[\]]+', line)
+                if 'mode:' in line_split:
+                    # mode: <enhanced|basic>
+                    zone_status['mode'] = line_split[line_split.index('mode:')
+                                                     + 1]
+                    continue
+                if 'session:' in line_split:
+                    # session: <none|a value other than none>
+                    zone_status['session'] = \
+                        line_split[line_split.index('session:') + 1]
+                    continue
+        except Exception as ex:
+            # In case of parsing error here, it should be malformed cli output.
+            msg = _("Malformed zone status: (switch=%(switch)s "
+                    "zone_config=%(zone_config)s)."
+                    ) % {'switch': self.switch_ip,
+                         'zone_status': switch_data}
+            LOG.error(msg)
+            exc_msg = _("Exception: %s") % six.text_type(ex)
+            LOG.error(exc_msg)
+            raise exception.FCZoneDriverException(reason=msg)
+
+        return zone_status
+
+    def delete_zones(self, zone_names, activate, fabric_vsan, active_zone_set,
+                     zone_status):
+        """Delete zones from fabric.
+
+        Method to delete the active zone config zones
+
+        params zone_names: zoneNames separated by semicolon
+        params activate: True/False
+        """
+
+        LOG.debug("zone_names %s", zone_names)
+        active_zoneset_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
+
+        cmds = [['conf'],
+                ['zoneset', 'name', active_zoneset_name, 'vsan',
+                 fabric_vsan]]
+
+        try:
+            for zone in set(zone_names.split(';')):
+                cmds.append(['no', 'zone', 'name', zone])
+
+            cmds.append(['end'])
+
+            LOG.debug("Delete zones: Config cmd to run: %s", cmds)
+            self._ssh_execute(cmds, True, 1)
+
+            if activate:
+                self.activate_zoneset(active_zoneset_name, fabric_vsan,
+                                      zone_status)
+            self._cfg_save()
+
+        except Exception as e:
+            msg = _("Deleting zones failed: (command=%(cmd)s error=%(err)s)."
+                    ) % {'cmd': cmds, 'err': six.text_type(e)}
+            LOG.error(msg)
+            raise exception.CiscoZoningCliException(reason=msg)
+
+    def get_nameserver_info(self):
+        """Get name server data from fabric.
+
+        This method will return the connected node port wwn list(local
+        and remote) for the given switch fabric
+
+        show fcns database
+        """
+        cli_output = None
+        return_list = []
+        try:
+            cli_output = self._get_switch_info([ZoneConstant.FCNS_SHOW,
+                                                self.fabric_vsan])
+        except exception.CiscoZoningCliException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE("Failed collecting fcns database "
+                              "info for fabric %s"), self.switch_ip)
+
+        if (cli_output):
+            return_list = self._parse_ns_output(cli_output)
+
+        LOG.info(_LI("Connector returning fcnsinfo-%s"), return_list)
+
+        return return_list
+
+    @utils.retry(processutils.ProcessExecutionError, retries=5)
+    def _cfg_save(self):
+        cmd = ['copy', 'running-config', 'startup-config']
+        self._run_ssh(cmd, True)
+
+    def _get_switch_info(self, cmd_list):
+        stdout, stderr, sw_data = None, None, None
+        try:
+            stdout, stderr = self._run_ssh(cmd_list, True)
+            LOG.debug("CLI output from ssh - output: %s", stdout)
+            if (stdout):
+                sw_data = stdout.splitlines()
+            return sw_data
+        except processutils.ProcessExecutionError as e:
+            msg = _("Error while getting data via ssh: (command=%(cmd)s "
+                    "error=%(err)s).") % {'cmd': cmd_list,
+                                          'err': six.text_type(e)}
+            LOG.error(msg)
+            raise exception.CiscoZoningCliException(reason=msg)
+
+    def _parse_ns_output(self, switch_data):
+        """Parses name server data.
+
+        Parses nameserver raw data and adds the device port wwns to the list
+
+        :returns: List -- list of device port wwn from ns info
+        """
+        return_list = []
+        for line in switch_data:
+            if not(" N " in line):
+                continue
+            linesplit = line.split()
+            if len(linesplit) > 2:
+                node_port_wwn = linesplit[2]
+                return_list.append(node_port_wwn)
+            else:
+                msg = _("Malformed show fcns database string: %s") % line
+                LOG.error(msg)
+                raise exception.InvalidParameterValue(err=msg)
+        return return_list
+
+    def _run_ssh(self, cmd_list, check_exit_code=True):
+
+        command = ' '.join(cmd_list)
+
+        if not self.sshpool:
+            self.sshpool = ssh_utils.SSHPool(self.switch_ip,
+                                             self.switch_port,
+                                             None,
+                                             self.switch_user,
+                                             self.switch_pwd,
+                                             min_size=1,
+                                             max_size=5)
+        try:
+            with self.sshpool.item() as ssh:
+                return processutils.ssh_execute(
+                    ssh,
+                    command,
+                    check_exit_code=check_exit_code)
+
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                LOG.warning(_LW("Error running SSH command: %s"), command)
+
+    def _ssh_execute(self, cmd_list, check_exit_code=True, attempts=1):
+        """Execute cli with status update.
+
+        Executes CLI commands where status return is expected.
+
+        cmd_list is a list of commands, where each command is itself
+        a list of parameters.  We use utils.check_ssh_injection to check each
+        command, but then join then with " ; " to form a single command.
+        """
+
+        # Check that each command is secure
+        for cmd in cmd_list:
+            utils.check_ssh_injection(cmd)
+
+        # Combine into a single command.
+        command = ' ; '.join(map(lambda x: ' '.join(x), cmd_list))
+
+        if not self.sshpool:
+            self.sshpool = ssh_utils.SSHPool(self.switch_ip,
+                                             self.switch_port,
+                                             None,
+                                             self.switch_user,
+                                             self.switch_pwd,
+                                             min_size=1,
+                                             max_size=5)
+        stdin, stdout, stderr = None, None, None
+        LOG.debug("Executing command via ssh: %s", command)
+        last_exception = None
+        try:
+            with self.sshpool.item() as ssh:
+                while attempts > 0:
+                    attempts -= 1
+                    try:
+                        stdin, stdout, stderr = ssh.exec_command(command)
+                        channel = stdout.channel
+                        exit_status = channel.recv_exit_status()
+                        LOG.debug("Exit Status from ssh: %s", exit_status)
+                        # exit_status == -1 if no exit code was returned
+                        if exit_status != -1:
+                            LOG.debug('Result was %s', exit_status)
+                            if check_exit_code and exit_status != 0:
+                                raise processutils.ProcessExecutionError(
+                                    exit_code=exit_status,
+                                    stdout=stdout,
+                                    stderr=stderr,
+                                    cmd=command)
+                            else:
+                                return True
+                        else:
+                            return True
+                    except Exception as e:
+                        LOG.exception(_LE('Error executing SSH command.'))
+                        last_exception = e
+                        greenthread.sleep(random.randint(20, 500) / 100.0)
+                LOG.debug("Handling error case after SSH: %s", last_exception)
+                try:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=last_exception.exit_code,
+                        stdout=last_exception.stdout,
+                        stderr=last_exception.stderr,
+                        cmd=last_exception.cmd)
+                except AttributeError:
+                    raise processutils.ProcessExecutionError(
+                        exit_code=-1,
+                        stdout="",
+                        stderr="Error running SSH command",
+                        cmd=command)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                LOG.exception(_LE("Error executing command via ssh."))
+        finally:
+            if stdin:
+                stdin.flush()
+                stdin.close()
+            if stdout:
+                stdout.close()
+            if stderr:
+                stderr.close()
+
+    def cleanup(self):
+        self.sshpool = None
diff --git a/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py b/cinder/zonemanager/drivers/cisco/cisco_fc_zone_driver.py
new file mode 100644 (file)
index 0000000..597d66b
--- /dev/null
@@ -0,0 +1,518 @@
+#    (c) Copyright 2014 Cisco 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.
+#
+
+
+"""
+Cisco Zone Driver is responsible to manage access control using FC zoning
+for Cisco FC fabrics.
+This is a concrete implementation of FCZoneDriver interface implementing
+add_connection and delete_connection interfaces.
+
+**Related Flags**
+
+:zone_activate: Used by: class: 'FCZoneDriver'. Defaults to True
+:zone_name_prefix: Used by: class: 'FCZoneDriver'. Defaults to 'openstack'
+"""
+
+from oslo_concurrency import lockutils
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import excutils
+from oslo_utils import importutils
+import six
+import string
+
+from cinder import exception
+from cinder.i18n import _, _LE, _LI
+from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as fabric_opts
+from cinder.zonemanager.drivers import driver_utils
+from cinder.zonemanager.drivers import fc_zone_driver
+from cinder.zonemanager import utils as zm_utils
+
+LOG = logging.getLogger(__name__)
+
+SUPPORTED_CHARS = string.ascii_letters + string.digits + '$' + '-' + '^' + '_'
+cisco_opts = [
+    cfg.StrOpt('cisco_sb_connector',
+               default='cinder.zonemanager.drivers.cisco'
+               '.cisco_fc_zone_client_cli.CiscoFCZoneClientCLI',
+               help='Southbound connector for zoning operation'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(cisco_opts, group='fc-zone-manager')
+
+
+class CiscoFCZoneDriver(fc_zone_driver.FCZoneDriver):
+    """Cisco FC zone driver implementation.
+
+    OpenStack Fibre Channel zone driver to manage FC zoning in
+    Cisco SAN fabrics.
+
+    Version history:
+        1.0 - Initial Cisco FC zone driver
+        1.1 - Added friendly zone name support
+    """
+
+    VERSION = "1.1.0"
+
+    def __init__(self, **kwargs):
+        super(CiscoFCZoneDriver, self).__init__(**kwargs)
+        self.configuration = kwargs.get('configuration', None)
+        if self.configuration:
+            self.configuration.append_config_values(cisco_opts)
+
+            # Adding a hack to handle parameters from super classes
+            # in case configured with multi backends.
+            fabric_names = self.configuration.safe_get('fc_fabric_names')
+            activate = self.configuration.safe_get('cisco_zone_activate')
+            prefix = self.configuration.safe_get('cisco_zone_name_prefix')
+            base_san_opts = []
+            if not fabric_names:
+                base_san_opts.append(
+                    cfg.StrOpt('fc_fabric_names',
+                               help='Comma separated list of fibre channel '
+                               'fabric names. This list of names is used to'
+                               ' retrieve other SAN credentials for connecting'
+                               ' to each SAN fabric'
+                               ))
+            if not activate:
+                base_san_opts.append(
+                    cfg.BoolOpt('cisco_zone_activate',
+                                default=True,
+                                help='Indicates whether zone should '
+                                'be activated or not'))
+            if not prefix:
+                base_san_opts.append(
+                    cfg.StrOpt('cisco_zone_name_prefix',
+                               default="openstack",
+                               help="A prefix to be used when naming zone"))
+            if len(base_san_opts) > 0:
+                CONF.register_opts(base_san_opts)
+                self.configuration.append_config_values(base_san_opts)
+            fabric_names = [x.strip() for x in self.
+                            configuration.fc_fabric_names.split(',')]
+
+            # There can be more than one SAN in the network and we need to
+            # get credentials for each SAN.
+            if fabric_names:
+                self.fabric_configs = fabric_opts.load_fabric_configurations(
+                    fabric_names)
+
+    @lockutils.synchronized('cisco', 'fcfabric-', True)
+    def add_connection(self, fabric, initiator_target_map, host_name=None,
+                       storage_system=None):
+        """Concrete implementation of add_connection.
+
+        Based on zoning policy and state of each I-T pair, list of zone
+        members are created and pushed to the fabric to add zones. The
+        new zones created or zones updated are activated based on isActivate
+        flag set in cinder.conf returned by volume driver after attach
+        operation.
+
+        :param fabric: Fabric name from cinder.conf file
+        :param initiator_target_map: Mapping of initiator to list of targets
+        """
+
+        LOG.debug("Add connection for Fabric: %s", fabric)
+        LOG.info(_LI("CiscoFCZoneDriver - Add connection "
+                     "for I-T map: %s"), initiator_target_map)
+        fabric_ip = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_address')
+        fabric_user = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_user')
+        fabric_pwd = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_password')
+        fabric_port = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_port')
+        zoning_policy = self.configuration.zoning_policy
+        zoning_policy_fab = self.fabric_configs[fabric].safe_get(
+            'cisco_zoning_policy')
+        if zoning_policy_fab:
+            zoning_policy = zoning_policy_fab
+
+        zoning_vsan = self.fabric_configs[fabric].safe_get('cisco_zoning_vsan')
+
+        LOG.info(_LI("Zoning policy for Fabric %s"), zoning_policy)
+
+        statusmap_from_fabric = self.get_zoning_status(
+            fabric_ip, fabric_user, fabric_pwd, fabric_port, zoning_vsan)
+
+        if statusmap_from_fabric.get('session') == 'none':
+
+            cfgmap_from_fabric = self.get_active_zone_set(
+                fabric_ip, fabric_user, fabric_pwd, fabric_port, zoning_vsan)
+            zone_names = []
+            if cfgmap_from_fabric.get('zones'):
+                zone_names = cfgmap_from_fabric['zones'].keys()
+                # based on zoning policy, create zone member list and
+                # push changes to fabric.
+                for initiator_key in initiator_target_map.keys():
+                    zone_map = {}
+                    initiator = initiator_key.lower()
+                    t_list = initiator_target_map[initiator_key]
+                    if zoning_policy == 'initiator-target':
+                        for t in t_list:
+                            target = t.lower()
+                            zone_members = [
+                                zm_utils.get_formatted_wwn(initiator),
+                                zm_utils.get_formatted_wwn(target)]
+                            zone_name = (
+                                driver_utils.get_friendly_zone_name(
+                                    zoning_policy,
+                                    initiator,
+                                    target,
+                                    host_name,
+                                    storage_system,
+                                    self.configuration.cisco_zone_name_prefix,
+                                    SUPPORTED_CHARS))
+                            if (len(cfgmap_from_fabric) == 0 or (
+                                    zone_name not in zone_names)):
+                                zone_map[zone_name] = zone_members
+                            else:
+                                # This is I-T zoning, skip if zone exists.
+                                LOG.info(_LI("Zone exists in I-T mode. "
+                                             "Skipping zone creation %s"),
+                                         zone_name)
+                    elif zoning_policy == 'initiator':
+                        zone_members = [
+                            zm_utils.get_formatted_wwn(initiator)]
+                        for t in t_list:
+                            target = t.lower()
+                            zone_members.append(
+                                zm_utils.get_formatted_wwn(target))
+
+                        zone_name = (
+                            driver_utils.get_friendly_zone_name(
+                                zoning_policy,
+                                initiator,
+                                target,
+                                host_name,
+                                storage_system,
+                                self.configuration.cisco_zone_name_prefix,
+                                SUPPORTED_CHARS))
+
+                        if len(zone_names) > 0 and (zone_name in zone_names):
+                            zone_members = zone_members + filter(
+                                lambda x: x not in zone_members,
+                                cfgmap_from_fabric['zones'][zone_name])
+                        zone_map[zone_name] = zone_members
+                    else:
+                        msg = _("Zoning Policy: %s, not"
+                                " recognized") % zoning_policy
+                        LOG.error(msg)
+                        raise exception.FCZoneDriverException(msg)
+
+                LOG.info(_LI("Zone map to add: %s"), zone_map)
+
+                if len(zone_map) > 0:
+                    conn = None
+                    try:
+                        conn = importutils.import_object(
+                            self.configuration.cisco_sb_connector,
+                            ipaddress=fabric_ip,
+                            username=fabric_user,
+                            password=fabric_pwd,
+                            port=fabric_port,
+                            vsan=zoning_vsan)
+                        conn.add_zones(
+                            zone_map, self.configuration.cisco_zone_activate,
+                            zoning_vsan, cfgmap_from_fabric,
+                            statusmap_from_fabric)
+                        conn.cleanup()
+                    except exception.CiscoZoningCliException as cisco_ex:
+                        msg = _("Exception: %s") % six.text_type(cisco_ex)
+                        raise exception.FCZoneDriverException(msg)
+                    except Exception:
+                        msg = _("Failed to add zoning configuration.")
+                        LOG.exception(msg)
+                        raise exception.FCZoneDriverException(msg)
+                LOG.debug("Zones added successfully: %s", zone_map)
+            else:
+                LOG.debug("Zoning session exists VSAN: %s", zoning_vsan)
+
+    @lockutils.synchronized('cisco', 'fcfabric-', True)
+    def delete_connection(self, fabric, initiator_target_map, host_name=None,
+                          storage_system=None):
+        """Concrete implementation of delete_connection.
+
+        Based on zoning policy and state of each I-T pair, list of zones
+        are created for deletion. The zones are either updated deleted based
+        on the policy and attach/detach state of each I-T pair.
+
+        :param fabric: Fabric name from cinder.conf file
+        :param initiator_target_map: Mapping of initiator to list of targets
+        """
+        LOG.debug("Delete connection for fabric: %s", fabric)
+        LOG.info(_LI("CiscoFCZoneDriver - Delete connection for I-T map: %s"),
+                 initiator_target_map)
+        fabric_ip = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_address')
+        fabric_user = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_user')
+        fabric_pwd = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_password')
+        fabric_port = self.fabric_configs[fabric].safe_get(
+            'cisco_fc_fabric_port')
+        zoning_policy = self.configuration.zoning_policy
+        zoning_policy_fab = self.fabric_configs[fabric].safe_get(
+            'cisco_zoning_policy')
+
+        if zoning_policy_fab:
+            zoning_policy = zoning_policy_fab
+
+        zoning_vsan = self.fabric_configs[fabric].safe_get('cisco_zoning_vsan')
+
+        LOG.info(_LI("Zoning policy for fabric %s"), zoning_policy)
+
+        statusmap_from_fabric = self.get_zoning_status(
+            fabric_ip, fabric_user, fabric_pwd, fabric_port, zoning_vsan)
+
+        if statusmap_from_fabric.get('session') == 'none':
+            cfgmap_from_fabric = self.get_active_zone_set(
+                fabric_ip, fabric_user, fabric_pwd, fabric_port, zoning_vsan)
+
+            zone_names = []
+            if cfgmap_from_fabric.get('zones'):
+                zone_names = cfgmap_from_fabric['zones'].keys()
+
+            # Based on zoning policy, get zone member list and push
+            # changes to fabric. This operation could result in an update
+            # for zone config with new member list or deleting zones from
+            # active cfg.
+
+            LOG.debug("zone config from Fabric: %s", cfgmap_from_fabric)
+            for initiator_key in initiator_target_map.keys():
+                initiator = initiator_key.lower()
+                formatted_initiator = zm_utils.get_formatted_wwn(initiator)
+                zone_map = {}
+                zones_to_delete = []
+                t_list = initiator_target_map[initiator_key]
+                if zoning_policy == 'initiator-target':
+                    # In this case, zone needs to be deleted.
+                    for t in t_list:
+                        target = t.lower()
+                        zone_name = (
+                            driver_utils.get_friendly_zone_name(
+                                zoning_policy,
+                                initiator,
+                                target,
+                                host_name,
+                                storage_system,
+                                self.configuration.cisco_zone_name_prefix,
+                                SUPPORTED_CHARS))
+                        LOG.debug("Zone name to del: %s", zone_name)
+                        if (len(zone_names) > 0 and (zone_name in zone_names)):
+                            # delete zone.
+                            LOG.debug("Added zone to delete to list: %s",
+                                      zone_name)
+                            zones_to_delete.append(zone_name)
+
+                elif zoning_policy == 'initiator':
+                    zone_members = [formatted_initiator]
+                    for t in t_list:
+                        target = t.lower()
+                        zone_members.append(
+                            zm_utils.get_formatted_wwn(target))
+
+                    zone_name = driver_utils.get_friendly_zone_name(
+                        zoning_policy,
+                        initiator,
+                        target,
+                        host_name,
+                        storage_system,
+                        self.configuration.cisco_zone_name_prefix,
+                        SUPPORTED_CHARS)
+
+                    if (zone_names and (zone_name in zone_names)):
+                        filtered_members = filter(
+                            lambda x: x not in zone_members,
+                            cfgmap_from_fabric['zones'][zone_name])
+
+                        # The assumption here is that initiator is always
+                        # there in the zone as it is 'initiator' policy.
+                        # We find the filtered list and if it is non-empty,
+                        # add initiator to it and update zone if filtered
+                        # list is empty, we remove that zone.
+                        LOG.debug("Zone delete - I mode: filtered targets: %s",
+                                  filtered_members)
+                        if filtered_members:
+                            filtered_members.append(formatted_initiator)
+                            LOG.debug("Filtered zone members to update: %s",
+                                      filtered_members)
+                            zone_map[zone_name] = filtered_members
+                            LOG.debug("Filtered zone Map to update: %s",
+                                      zone_map)
+                        else:
+                            zones_to_delete.append(zone_name)
+                else:
+                    LOG.info(_LI("Zoning Policy: %s, not recognized"),
+                             zoning_policy)
+                LOG.debug("Final Zone map to update: %s", zone_map)
+                LOG.debug("Final Zone list to delete: %s", zones_to_delete)
+                conn = None
+                try:
+                    conn = importutils.import_object(
+                        self.configuration.cisco_sb_connector,
+                        ipaddress=fabric_ip,
+                        username=fabric_user,
+                        password=fabric_pwd,
+                        port=fabric_port,
+                        vsan=zoning_vsan)
+                    # Update zone membership.
+                    if zone_map:
+                        conn.add_zones(
+                            zone_map, self.configuration.cisco_zone_activate,
+                            zoning_vsan, cfgmap_from_fabric,
+                            statusmap_from_fabric)
+                    # Delete zones ~sk.
+                    if zones_to_delete:
+                        zone_name_string = ''
+                        num_zones = len(zones_to_delete)
+                        for i in range(0, num_zones):
+                            if i == 0:
+                                zone_name_string = ('%s%s' % (
+                                                    zone_name_string,
+                                                    zones_to_delete[i]))
+                            else:
+                                zone_name_string = ('%s%s%s' % (
+                                                    zone_name_string, ';',
+                                                    zones_to_delete[i]))
+
+                        conn.delete_zones(zone_name_string,
+                                          self.configuration.
+                                          cisco_zone_activate,
+                                          zoning_vsan, cfgmap_from_fabric,
+                                          statusmap_from_fabric)
+                    conn.cleanup()
+                except Exception:
+                    msg = _("Failed to update or delete zoning configuration")
+                    LOG.exception(msg)
+                    raise exception.FCZoneDriverException(msg)
+                LOG.debug("Zones deleted successfully: %s", zone_map)
+            else:
+                LOG.debug("Zoning session exists VSAN: %s", zoning_vsan)
+
+    def get_san_context(self, target_wwn_list):
+        """Lookup SAN context for visible end devices.
+
+        Look up each SAN configured and return a map of SAN (fabric IP) to
+        list of target WWNs visible to the fabric.
+        """
+        formatted_target_list = []
+        fabric_map = {}
+        fabrics = [x.strip() for x in self.
+                   configuration.fc_fabric_names.split(',')]
+        LOG.debug("Fabric List: %s", fabrics)
+        LOG.debug("Target wwn List: %s", target_wwn_list)
+        if len(fabrics) > 0:
+            for t in target_wwn_list:
+                formatted_target_list.append(
+                    zm_utils.get_formatted_wwn(t.lower()))
+            LOG.debug("Formatted Target wwn List: %s", formatted_target_list)
+            for fabric_name in fabrics:
+                fabric_ip = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_address')
+                fabric_user = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_user')
+                fabric_pwd = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_password')
+                fabric_port = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_fc_fabric_port')
+                zoning_vsan = self.fabric_configs[fabric_name].safe_get(
+                    'cisco_zoning_vsan')
+
+                # Get name server data from fabric and get the targets
+                # logged in.
+                nsinfo = None
+                try:
+                    conn = importutils.import_object(
+                        self.configuration.cisco_sb_connector,
+                        ipaddress=fabric_ip,
+                        username=fabric_user,
+                        password=fabric_pwd, port=fabric_port,
+                        vsan=zoning_vsan)
+                    nsinfo = conn.get_nameserver_info()
+                    LOG.debug("show fcns database info from fabric: %s",
+                              nsinfo)
+                    conn.cleanup()
+                except exception.CiscoZoningCliException:
+                    with excutils.save_and_reraise_exception():
+                        LOG.exception(_LE("Error getting show fcns database "
+                                          "info."))
+                except Exception:
+                    msg = _("Failed to get show fcns database info.")
+                    LOG.exception(msg)
+                    raise exception.FCZoneDriverException(msg)
+                visible_targets = filter(
+                    lambda x: x in formatted_target_list, nsinfo)
+
+                if visible_targets:
+                    LOG.info(_LI("Filtered targets for SAN is: %s"),
+                             {fabric_name: visible_targets})
+                    # getting rid of the ':' before returning
+                    for idx, elem in enumerate(visible_targets):
+                        visible_targets[idx] = six.text_type(
+                            visible_targets[idx]).replace(':', '')
+                    fabric_map[fabric_name] = visible_targets
+                else:
+                    LOG.debug("No targets are in the fcns info for SAN %s",
+                              fabric_name)
+        LOG.debug("Return SAN context output: %s", fabric_map)
+        return fabric_map
+
+    def get_active_zone_set(self, fabric_ip,
+                            fabric_user, fabric_pwd, fabric_port,
+                            zoning_vsan):
+        """Gets active zoneset config for vsan."""
+        cfgmap = {}
+        conn = None
+        try:
+            LOG.debug("Southbound connector: %s",
+                      self.configuration.cisco_sb_connector)
+            conn = importutils.import_object(
+                self.configuration.cisco_sb_connector,
+                ipaddress=fabric_ip, username=fabric_user,
+                password=fabric_pwd, port=fabric_port, vsan=zoning_vsan)
+            cfgmap = conn.get_active_zone_set()
+            conn.cleanup()
+        except Exception:
+            msg = _("Failed to access active zoning configuration.")
+            LOG.exception(msg)
+            raise exception.FCZoneDriverException(msg)
+        LOG.debug("Active zone set from fabric: %s", cfgmap)
+        return cfgmap
+
+    def get_zoning_status(self, fabric_ip, fabric_user, fabric_pwd,
+                          fabric_port, zoning_vsan):
+        """Gets zoneset status and mode."""
+        statusmap = {}
+        conn = None
+        try:
+            LOG.debug("Southbound connector: %s",
+                      self.configuration.cisco_sb_connector)
+            conn = importutils.import_object(
+                self.configuration.cisco_sb_connector,
+                ipaddress=fabric_ip, username=fabric_user,
+                password=fabric_pwd, port=fabric_port, vsan=zoning_vsan)
+            statusmap = conn.get_zoning_status()
+            conn.cleanup()
+        except Exception:
+            msg = _("Failed to access zoneset status:%s")
+            LOG.exception(msg)
+            raise exception.FCZoneDriverException(msg)
+        LOG.debug("Zoneset status from fabric: %s", statusmap)
+        return statusmap
diff --git a/cinder/zonemanager/drivers/cisco/fc_zone_constants.py b/cinder/zonemanager/drivers/cisco/fc_zone_constants.py
new file mode 100644 (file)
index 0000000..6e1a875
--- /dev/null
@@ -0,0 +1,32 @@
+#    (c) Copyright 2014 Cisco 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.
+#
+
+
+"""
+Common constants used by Cisco FC Zone Driver.
+"""
+ACTIVE_ZONE_CONFIG = 'active_zone_config'
+CFG_ZONESET = 'zoneset'
+CFG_ZONE = 'zone'
+CFG_ZONE_MEMBER = 'pwwn'
+CFG_ZONES = 'zones'
+
+"""
+CLI Commands for FC zoning operations.
+"""
+GET_ACTIVE_ZONE_CFG = 'show zoneset active vsan '
+FCNS_SHOW = 'show fcns database vsan '
+GET_ZONE_STATUS = 'show zone status vsan '
diff --git a/releasenotes/notes/cisco-fczm-removed-502dfcba51acd1b4.yaml b/releasenotes/notes/cisco-fczm-removed-502dfcba51acd1b4.yaml
deleted file mode 100644 (file)
index 29949a5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
----
-upgrade:
-  - The Cisco FC Zone Manager driver is no longer supported
-    and not included in-tree.