message = _("Fibre Channel Zoning CLI error: %(reason)s")
+class CiscoZoningCliException(CinderException):
+ message = _("Fibre Channel Zoning CLI error: %(reason)s")
+
+
class NetAppDriverException(VolumeDriverException):
message = _("NetApp Cinder Driver exception.")
--- /dev/null
+# (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
+from cinder.zonemanager.utils import get_formatted_wwn
+
+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()
+
+ # 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.IntOpt('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, _device_map_to_verify)
+
+ @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, ns_info_list_expected)
+
+ 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(return_wwn_list, expected_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(get_formatted_wwn(wwn_list[0]))
+ self.assertEqual(return_wwn_list, expected_wwn_list)
+
+
+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 close(self):
+ pass
+
+ def flush(self):
+ self.buffer = ''
--- /dev/null
+# (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."""
+
+from mock import patch
+
+from cinder import exception
+from cinder.openstack.common import processutils
+from cinder import test
+from cinder.zonemanager.drivers.cisco.cisco_fc_zone_client_cli \
+ import CiscoFCZoneClientCLI
+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'],
+ 'openstack50060b0000c26602201900051ee8e327':
+ ['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(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)
+
+ @patch.object(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_returned, active_zoneset)
+
+ @patch.object(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)
+
+ @patch.object(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_returned, zoning_status_basic)
+
+ @patch.object(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_returned,
+ zoning_status_enhanced_nosess)
+
+ @patch.object(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_returned,
+ zoning_status_enhanced_sess)
+
+ @patch.object(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, ns_info_list_expected)
+
+ @patch.object(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)
+
+ @patch.object(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, 1)
+
+ @patch.object(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(switch_data, nsshow_list)
+ run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
+
+ 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(return_wwn_list, expected_wwn_list)
+
+
+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 = ''
--- /dev/null
+# (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.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import importutils
+from cinder.openstack.common import processutils
+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.zonemanager.'
+ 'test_cisco_fc_zone_driver.'
+ 'FakeCiscoFCZoneDriver')
+ configuration.cisco_sb_connector = ('cinder.tests.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.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 = {}
--- /dev/null
+# (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.fc_san_lookup_service import FCSanLookupService
+
+_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(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.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, _device_map_to_verify)
+
+ 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
--- /dev/null
+# (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.configuration 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.IntOpt('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',
+ default=None,
+ help='overridden zone name prefix'),
+ cfg.StrOpt('cisco_zoning_vsan',
+ default=None,
+ help='VSAN of the Fabric'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(cisco_zone_opts, 'CISCO_FABRIC_EXAMPLE')
+
+
+def load_fabric_configurations(fabric_names):
+ fabric_configs = {}
+ for fabric_name in fabric_names:
+ config = Configuration(cisco_zone_opts, fabric_name)
+ fabric_configs[fabric_name] = config
+
+ return fabric_configs
--- /dev/null
+# (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
+import six
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+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 ZoneConstant
+from cinder.zonemanager.fc_san_lookup_service import FCSanLookupService
+from cinder.zonemanager.utils import get_formatted_wwn
+
+LOG = logging.getLogger(__name__)
+
+
+class CiscoFCSanLookupService(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__(self, **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
+
+ self.fabric_configs = ""
+
+ 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(get_formatted_wwn(t))
+
+ for i in initiator_wwn_list:
+ formatted_initiator_list.append(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 = filter(lambda x: x in formatted_target_list,
+ nsinfo)
+ visible_initiators = filter(lambda x: x in
+ formatted_initiator_list, nsinfo)
+
+ 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 = ZoneConstant.FCNS_SHOW + fabric_vsan + ' | no-more'
+ cli_output = self._get_switch_info(cmd)
+ except exception.FCSanLookupServiceException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("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(_("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
--- /dev/null
+# (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
+import six
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+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(_("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.exception(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(_("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(_("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.exception(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(_("Failed collecting fcns database "
+ "info for fabric %s"), self.switch_ip)
+
+ if (cli_output):
+ return_list = self._parse_ns_output(cli_output)
+
+ LOG.info(_("Connector returning fcnsinfo-%s"), return_list)
+
+ return return_list
+
+ def _cfg_save(self):
+ cmd = ['copy', 'running-config', 'startup-config']
+ self._run_ssh(cmd, True, 1)
+
+ 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 -- 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, 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(_("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
--- /dev/null
+# (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.config import cfg
+import six
+
+from cinder import exception
+from cinder.i18n import _
+from cinder.openstack.common import excutils
+from cinder.openstack.common import importutils
+from cinder.openstack.common import lockutils
+from cinder.openstack.common import log as logging
+from cinder.zonemanager.drivers.cisco import cisco_fabric_opts as fabric_opts
+from cinder.zonemanager.drivers.fc_zone_driver import FCZoneDriver
+from cinder.zonemanager.utils import get_formatted_wwn
+
+LOG = logging.getLogger(__name__)
+
+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, 'fc-zone-manager')
+
+
+class CiscoFCZoneDriver(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
+ """
+
+ VERSION = "1.0.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', default=None,
+ 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):
+ """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(_("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(_("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 = [get_formatted_wwn(initiator),
+ get_formatted_wwn(target)]
+ zone_name = (self.
+ configuration.cisco_zone_name_prefix
+ + initiator.replace(':', '')
+ + target.replace(':', ''))
+ 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(_("Zone exists in I-T mode. "
+ "Skipping zone creation %s"),
+ zone_name)
+ elif zoning_policy == 'initiator':
+ zone_members = [get_formatted_wwn(initiator)]
+ for t in t_list:
+ target = t.lower()
+ zone_members.append(get_formatted_wwn(target))
+
+ zone_name = self.configuration.cisco_zone_name_prefix \
+ + initiator.replace(':', '')
+
+ 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(_("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 as e:
+ LOG.error(_("Exception: %s") % six.text_type(e))
+ msg = (_("Failed to add zoning configuration %s") %
+ six.text_type(e))
+ 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):
+ """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(_("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(_("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 = 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 = (
+ self.configuration.cisco_zone_name_prefix
+ + initiator.replace(':', '')
+ + target.replace(':', ''))
+ 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(get_formatted_wwn(target))
+
+ zone_name = self.configuration.cisco_zone_name_prefix \
+ + initiator.replace(':', '')
+
+ 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(_("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 as e:
+ msg = _("Exception: %s") % six.text_type(e)
+ LOG.error(msg)
+ msg = _("Failed to update or delete zoning configuration")
+ 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(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 as ex:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Error getting show fcns database "
+ "info: %s"), six.text_type(ex))
+ except Exception as e:
+ msg = (_("Failed to get show fcns database info:%s") %
+ six.text_type(e))
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ visible_targets = filter(
+ lambda x: x in formatted_target_list, nsinfo)
+
+ if visible_targets:
+ LOG.info(_("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 as e:
+ msg = (_("Failed to access active zoning configuration:%s") %
+ six.text_type(e))
+ LOG.error(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 as e:
+ msg = (_("Failed to access zoneset status:%s") %
+ six.text_type(e))
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ LOG.debug("Zoneset status from fabric: %s", statusmap)
+ return statusmap
--- /dev/null
+# (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 '
return None
+def get_formatted_wwn(wwn_str):
+ """Utility API that formats WWN to insert ':'."""
+ if (len(wwn_str) != 16):
+ return wwn_str.lower()
+ else:
+ return (':'.join([wwn_str[i:i + 2]
+ for i in range(0, len(wwn_str), 2)])).lower()
+
+
def AddFCZone(initialize_connection):
"""Decorator to add a FC Zone."""
def decorator(self, *args, **kwargs):
#principal_switch_wwn=<None>
+[CISCO_FABRIC_EXAMPLE]
+
+#
+# Options defined in cinder.zonemanager.drivers.cisco.cisco_fabric_opts
+#
+
+# Management IP of fabric (string value)
+#cisco_fc_fabric_address=
+
+# Fabric user ID (string value)
+#cisco_fc_fabric_user=
+
+# Password for user (string value)
+#cisco_fc_fabric_password=
+
+# Connecting port (integer value)
+#cisco_fc_fabric_port=22
+
+# overridden zoning policy (string value)
+#cisco_zoning_policy=initiator-target
+
+# overridden zoning activation state (boolean value)
+#cisco_zone_activate=true
+
+# overridden zone name prefix (string value)
+#cisco_zone_name_prefix=<None>
+
+# VSAN of the Fabric (string value)
+#cisco_zoning_vsan=<None>
+
+
[database]
#
#brcd_sb_connector=cinder.zonemanager.drivers.brocade.brcd_fc_zone_client_cli.BrcdFCZoneClientCLI
+#
+# Options defined in cinder.zonemanager.drivers.cisco.cisco_fc_zone_driver
+#
+
+# Southbound connector for zoning operation (string value)
+#cisco_sb_connector=cinder.zonemanager.drivers.cisco.cisco_fc_zone_client_cli.CiscoFCZoneClientCLI
+
+
#
# Options defined in cinder.zonemanager.fc_zone_manager
#