class HPMSANotEnoughSpace(HPMSAVolumeDriverException):
message = _("Not enough space on VDisk (%(vdisk)s)")
+
+
+# Fibre Channel Zone Manager
+class ZoneManagerException(CinderException):
+ message = _("Fibre Channel connection control failure: %(reason)s")
+
+
+class FCZoneDriverException(CinderException):
+ message = _("Fibre Channel Zone operation failed: %(reason)s")
+
+
+class FCSanLookupServiceException(CinderException):
+ message = _("Fibre Channel SAN Lookup failure: %(reason)s")
+
+
+class BrocadeZoningCliException(CinderException):
+ message = _("Fibre Channel Zoning CLI error: %(reason)s")
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+"""Unit tests for brcd fc san lookup service."""
+
+import mock
+import paramiko
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume import configuration as conf
+from cinder.zonemanager.drivers.brocade.brcd_fc_san_lookup_service \
+ import BrcdFCSanLookupService
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+from mock import patch
+
+LOG = logging.getLogger(__name__)
+
+nsshow = '20:1a:00:05:1e:e8:e3:29'
+switch_data = [' N 011a00;2,3;20:1a:00:05:1e:e8:e3:29;\
+ 20:1a:00:05:1e:e8:e3:29;na']
+nsshow_data = ['10:00:8c:7c:ff:52:3b:01', '20:24:00:02:ac:00:0a:50']
+_device_map_to_verify = {
+ '100000051e55a100': {
+ 'initiator_port_wwn_list': ['10008c7cff523b01'],
+ 'target_port_wwn_list': ['20240002ac000a50']}}
+
+
+class TestBrcdFCSanLookupService(BrcdFCSanLookupService, test.TestCase):
+
+ def setUp(self):
+ super(TestBrcdFCSanLookupService, self).setUp()
+ self.client = paramiko.SSHClient()
+ self.configuration = conf.Configuration(None)
+ self.configuration.set_default('fc_fabric_names', 'BRCD_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('fc_fabric_address_BRCD_FAB_2',
+ default='10.24.49.100', help=''))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_user_BRCD_FAB_2',
+ default='admin', help=''))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_password_BRCD_FAB_2',
+ default='password', help='',
+ secret=True))
+ fc_fabric_opts.append(cfg.IntOpt('fc_fabric_port_BRCD_FAB_2',
+ default=22, help=''))
+ fc_fabric_opts.append(cfg.StrOpt('principal_switch_wwn_BRCD_FAB_2',
+ default='100000051e55a100', help=''))
+ self.configuration.append_config_values(fc_fabric_opts)
+
+ @patch.object(BrcdFCSanLookupService, 'get_nameserver_info')
+ def test_get_device_mapping_from_network(self, get_nameserver_info_mock):
+ initiator_list = ['10008c7cff523b01']
+ target_list = ['20240002ac000a50', '20240002ac000a40']
+ with mock.patch.object(self.client, 'connect') \
+ as client_connect_mock:
+ 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)
+
+ @patch.object(BrcdFCSanLookupService, '_get_switch_data')
+ 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',
+ '20:1a:00:05:1e:e8:e3:29']
+ get_switch_data_mock.return_value = (switch_data)
+ ns_info_list = self.get_nameserver_info()
+ self.assertEqual(ns_info_list, ns_info_list_expected)
+
+ def test__get_switch_data(self):
+ cmd = ZoneConstant.NS_SHOW
+
+ with mock.patch.object(self.client, 'exec_command') \
+ as exec_command_mock:
+ exec_command_mock.return_value = (Stream(),
+ Stream(nsshow),
+ Stream())
+ switch_data = self._get_switch_data(cmd)
+ self.assertEqual(switch_data, nsshow)
+ exec_command_mock.assert_called_once_with(cmd)
+
+ def test__parse_ns_output(self):
+ invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
+ return_wwn_list = []
+ expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29']
+ return_wwn_list = self._parse_ns_output(switch_data)
+ self.assertEqual(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(self.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 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+"""Unit tests for brcd fc zone client cli."""
+
+import mock
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+from cinder import test
+from cinder.zonemanager.drivers.brocade.brcd_fc_zone_client_cli \
+ import BrcdFCZoneClientCLI
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+from mock import patch
+
+LOG = logging.getLogger(__name__)
+
+nsshow = '20:1a:00:05:1e:e8:e3:29'
+switch_data = [' N 011a00;2,3;20:1a:00:05:1e:e8:e3:29;\
+ 20:1a:00:05:1e:e8:e3:29;na',
+ ' Fabric Port Name: 20:1a:00:05:1e:e8:e3:29']
+cfgactvshow = ['Effective configuration:\n',
+ ' cfg:\tOpenStack_Cfg\t\n',
+ ' zone:\topenstack50060b0000c26604201900051ee8e329\t\n',
+ '\t\t50:06:0b:00:00:c2:66:04\n',
+ '\t\t20: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'}
+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'
+supported_firmware = ['Kernel: 2.6', 'Fabric OS: v7.0.1']
+unsupported_firmware = ['Fabric OS: v6.2.1']
+
+
+class TestBrcdFCZoneClientCLI(BrcdFCZoneClientCLI, test.TestCase):
+
+ def setUp(self):
+ super(TestBrcdFCZoneClientCLI, self).setUp()
+
+ # override some of the functions
+ def __init__(self, *args, **kwargs):
+ test.TestCase.__init__(self, *args, **kwargs)
+
+ @patch.object(BrcdFCZoneClientCLI, '_get_switch_info')
+ def test_get_active_zone_set(self, get_switch_info_mock):
+ cmd_list = [ZoneConstant.GET_ACTIVE_ZONE_CFG]
+ get_switch_info_mock.return_value = cfgactvshow
+ active_zoneset_returned = self.get_active_zone_set()
+ get_switch_info_mock.assert_called_once_with(cmd_list)
+ self.assertDictMatch(active_zoneset_returned, active_zoneset)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test_get_active_zone_set_ssh_error(self, run_ssh_mock):
+ run_ssh_mock.side_effect = processutils.ProcessExecutionError
+ self.assertRaises(exception.BrocadeZoningCliException,
+ self.get_active_zone_set)
+
+ @mock.patch.object(BrcdFCZoneClientCLI, 'get_active_zone_set')
+ @mock.patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ @mock.patch.object(BrcdFCZoneClientCLI, '_cfg_save')
+ def test_add_zones_new_zone_no_activate(self, get_active_zs_mock,
+ apply_zone_change_mock,
+ cfg_save_mock):
+ get_active_zs_mock.return_value = active_zoneset
+ self.add_zones(new_zones, False)
+ get_active_zs_mock.assert_called_once()
+ apply_zone_change_mock.assert_called_twice()
+ cfg_save_mock.assert_called_once()
+
+ @mock.patch.object(BrcdFCZoneClientCLI, 'get_active_zone_set')
+ @mock.patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ @mock.patch.object(BrcdFCZoneClientCLI, '_cfg_save')
+ @mock.patch.object(BrcdFCZoneClientCLI, 'activate_zoneset')
+ def test_add_zones_new_zone_activate(self, get_active_zs_mock,
+ apply_zone_change_mock,
+ cfg_save_mock,
+ activate_zoneset_mock):
+ get_active_zs_mock.return_value = active_zoneset
+ self.add_zones(new_zone, True)
+ get_active_zs_mock.assert_called_once()
+ apply_zone_change_mock.assert_called_once()
+ cfg_save_mock.assert_called_once()
+ activate_zoneset_mock.assert_called_once()
+
+ @mock.patch.object(BrcdFCZoneClientCLI, '_ssh_execute')
+ def test_activate_zoneset(self, ssh_execute_mock):
+ ssh_execute_mock.return_value = True
+ return_value = self.activate_zoneset('zoneset1')
+ self.assertTrue(return_value)
+
+ @mock.patch.object(BrcdFCZoneClientCLI, '_ssh_execute')
+ def test_deactivate_zoneset(self, ssh_execute_mock):
+ ssh_execute_mock.return_value = True
+ return_value = self.deactivate_zoneset()
+ self.assertTrue(return_value)
+
+ @mock.patch.object(BrcdFCZoneClientCLI, 'get_active_zone_set')
+ @mock.patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ @mock.patch.object(BrcdFCZoneClientCLI, '_cfg_save')
+ def test_delete_zones_activate_false(self, get_active_zs_mock,
+ apply_zone_change_mock,
+ cfg_save_mock):
+ get_active_zs_mock.return_value = active_zoneset_multiple_zones
+ with mock.patch.object(self, '_zone_delete') \
+ as zone_delete_mock:
+ self.delete_zones(zone_names_to_delete, False)
+ get_active_zs_mock.assert_called_once()
+ apply_zone_change_mock.assert_called_once()
+ zone_delete_mock.assert_called_once_with(zone_names_to_delete)
+ cfg_save_mock.assert_called_once()
+
+ @patch.object(BrcdFCZoneClientCLI, 'get_active_zone_set')
+ @patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ @patch.object(BrcdFCZoneClientCLI, '_cfg_save')
+ @patch.object(BrcdFCZoneClientCLI, 'activate_zoneset')
+ def test_delete_zones_activate_true(self, get_active_zs_mock,
+ apply_zone_change_mock,
+ cfg_save_mock,
+ activate_zs_mock):
+ get_active_zs_mock.return_value = active_zoneset_multiple_zones
+ with mock.patch.object(self, '_zone_delete') \
+ as zone_delete_mock:
+ self.delete_zones(zone_names_to_delete, True)
+ get_active_zs_mock.assert_called_once()
+ apply_zone_change_mock.assert_called_once()
+ zone_delete_mock.assert_called_once_with(zone_names_to_delete)
+ cfg_save_mock.assert_called_once()
+ activate_zs_mock.assert_called_once()
+
+ @patch.object(BrcdFCZoneClientCLI, '_get_switch_info')
+ def test_get_nameserver_info(self, get_switch_info_mock):
+ ns_info_list = []
+ ns_info_list_expected = ['20:1a:00:05:1e:e8:e3:29',
+ '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(BrcdFCZoneClientCLI, '_run_ssh')
+ def test_get_nameserver_info_ssh_error(self, run_ssh_mock):
+ run_ssh_mock.side_effect = processutils.ProcessExecutionError
+ self.assertRaises(exception.BrocadeZoningCliException,
+ self.get_nameserver_info)
+
+ @patch.object(BrcdFCZoneClientCLI, '_ssh_execute')
+ def test__cfg_save(self, ssh_execute_mock):
+ cmd_list = [ZoneConstant.CFG_SAVE]
+ self._cfg_save()
+ ssh_execute_mock.assert_called_once_with(cmd_list, True, 1)
+
+ @patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ def test__zone_delete(self, apply_zone_change_mock):
+ zone_name = 'testzone'
+ cmd_list = ['zonedelete', '"testzone"']
+ self._zone_delete(zone_name)
+ apply_zone_change_mock.assert_called_once_with(cmd_list)
+
+ @patch.object(BrcdFCZoneClientCLI, 'apply_zone_change')
+ def test__cfg_trans_abort(self, apply_zone_change_mock):
+ cmd_list = [ZoneConstant.CFG_ZONE_TRANS_ABORT]
+ with mock.patch.object(self, '_is_trans_abortable') \
+ as is_trans_abortable_mock:
+ is_trans_abortable_mock.return_value = True
+ self._cfg_trans_abort()
+ is_trans_abortable_mock.assert_called_once()
+ apply_zone_change_mock.assert_called_once_with(cmd_list)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test__is_trans_abortable_true(self, run_ssh_mock):
+ cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
+ run_ssh_mock.return_value = (Stream(ZoneConstant.TRANS_ABORTABLE),
+ None)
+ data = self._is_trans_abortable()
+ self.assertTrue(data)
+ run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test__is_trans_abortable_ssh_error(self, run_ssh_mock):
+ run_ssh_mock.return_value = (Stream(), Stream())
+ self.assertRaises(exception.BrocadeZoningCliException,
+ self._is_trans_abortable)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test__is_trans_abortable_false(self, run_ssh_mock):
+ cmd_list = [ZoneConstant.CFG_SHOW_TRANS]
+ cfgtransshow = 'There is no outstanding zoning transaction'
+ run_ssh_mock.return_value = (Stream(cfgtransshow), None)
+ data = self._is_trans_abortable()
+ self.assertFalse(data)
+ run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test_apply_zone_change(self, run_ssh_mock):
+ cmd_list = [ZoneConstant.CFG_SAVE]
+ run_ssh_mock.return_value = (None, None)
+ self.apply_zone_change(cmd_list)
+ run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
+
+ @patch.object(BrcdFCZoneClientCLI, '_run_ssh')
+ def test__get_switch_info(self, run_ssh_mock):
+ cmd_list = [ZoneConstant.NS_SHOW]
+ 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):
+ invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
+ return_wwn_list = []
+ expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29']
+ return_wwn_list = self._parse_ns_output(switch_data)
+ self.assertEqual(return_wwn_list, expected_wwn_list)
+ self.assertRaises(exception.InvalidParameterValue,
+ self._parse_ns_output, invalid_switch_data)
+
+ @patch.object(BrcdFCZoneClientCLI, '_execute_shell_cmd')
+ def test_is_supported_firmware(self, exec_shell_cmd_mock):
+ exec_shell_cmd_mock.return_value = (supported_firmware, None)
+ self.assertTrue(self.is_supported_firmware())
+
+ @patch.object(BrcdFCZoneClientCLI, '_execute_shell_cmd')
+ def test_is_supported_firmware_invalid(self, exec_shell_cmd_mock):
+ exec_shell_cmd_mock.return_value = (unsupported_firmware, None)
+ self.assertFalse(self.is_supported_firmware())
+
+ @patch.object(BrcdFCZoneClientCLI, '_execute_shell_cmd')
+ def test_is_supported_firmware_no_ssh_response(self, exec_shell_cmd_mock):
+ exec_shell_cmd_mock.return_value = (None, Stream())
+ self.assertFalse(self.is_supported_firmware())
+
+ @patch.object(BrcdFCZoneClientCLI, '_execute_shell_cmd')
+ def test_is_supported_firmware_ssh_error(self, exec_shell_cmd_mock):
+ exec_shell_cmd_mock.side_effect = processutils.ProcessExecutionError
+ self.assertRaises(exception.BrocadeZoningCliException,
+ self.is_supported_firmware)
+
+
+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 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 Brocade fc zone driver."""
+
+import paramiko
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import importutils
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume import configuration as conf
+
+LOG = logging.getLogger(__name__)
+
+_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']), 't_zone': ['1,0']},
+ 'active_zone_config': 'cfg1'}
+_activate = True
+_zone_name = 'openstack10008c7cff523b0120240002ac000a50'
+_target_ns_map = {'100000051e55a100': ['20240002ac000a50']}
+_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 = {
+ '100000051e55a100': {
+ 'initiator_port_wwn_list': [
+ '10008c7cff523b01'], 'target_port_wwn_list': ['20240002ac000a50']}}
+_fabric_wwn = '100000051e55a100'
+
+
+class BrcdFcZoneDriverBaseTest(object):
+
+ def setup_config(self, is_normal, mode):
+ fc_test_opts = [
+ cfg.StrOpt('fc_fabric_address_BRCD_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.test_brcd_fc_zone_driver'
+ '.FakeBrcdFCZoneDriver')
+ configuration.brcd_sb_connector = ('cinder.tests.'
+ 'test_brcd_fc_zone_driver'
+ '.FakeBrcdFCZoneClientCLI')
+ configuration.zoning_policy = 'initiator-target'
+ configuration.zone_activate = True
+ configuration.zone_name_prefix = 'openstack'
+ configuration.fc_san_lookup_service = ('cinder.tests.'
+ 'test_brcd_fc_zone_driver.'
+ 'FakeBrcdFCSanLookupService')
+
+ configuration.fc_fabric_names = 'BRCD_FAB_1'
+ configuration.fc_fabric_address_BRCD_FAB_1 = '10.24.48.213'
+ if (is_normal):
+ configuration.fc_fabric_user_BRCD_FAB_1 = 'admin'
+ else:
+ configuration.fc_fabric_user_BRCD_FAB_1 = 'invaliduser'
+ configuration.fc_fabric_password_BRCD_FAB_1 = 'password'
+
+ if (mode == 1):
+ configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
+ elif (mode == 2):
+ configuration.zoning_policy_BRCD_FAB_1 = 'initiator'
+ else:
+ configuration.zoning_policy_BRCD_FAB_1 = 'initiator-target'
+ configuration.zone_activate_BRCD_FAB_1 = True
+ configuration.zone_name_prefix_BRCD_FAB_1 = 'openstack_fab1'
+ configuration.principal_switch_wwn_BRCD_FAB_1 = '100000051e55a100'
+ return configuration
+
+
+class TestBrcdFcZoneDriver(BrcdFcZoneDriverBaseTest, test.TestCase):
+
+ def setUp(self):
+ super(TestBrcdFcZoneDriver, 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.brocade.brcd_fc_zone_driver'
+ '.BrcdFCZoneDriver', configuration=config)
+
+ def fake_get_active_zone_set(self, fabric_ip, fabric_user, fabric_pwd):
+ return GlobalVars._active_cfg
+
+ def fake_get_san_context(self, target_wwn_list):
+ fabric_map = {}
+ return fabric_map
+
+ def test_add_connection(self):
+ """Normal flow for i-t mode."""
+ GlobalVars._active_cfg = _active_cfg_before_add
+ GlobalVars._is_normal_test = True
+ GlobalVars._zone_state = []
+ LOG.info(_("In Add GlobalVars._active_cfg:"
+ " %s"), GlobalVars._active_cfg)
+ LOG.info(_("In Add GlobalVars._is_normal_test: "
+ "%s"), GlobalVars._is_normal_test)
+ LOG.info(_("In Add GlobalVars._zone_state:"
+ " %s"), GlobalVars._zone_state)
+ self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
+ self.assertTrue(_zone_name in GlobalVars._zone_state)
+
+ def test_delete_connection(self):
+ GlobalVars._is_normal_test = True
+ GlobalVars._active_cfg = _active_cfg_before_delete
+ self.driver.delete_connection(
+ 'BRCD_FAB_1', _initiator_target_map)
+ self.assertFalse(_zone_name in GlobalVars._zone_state)
+
+ def test_add_connection_for_initiator_mode(self):
+ """Normal flow for i mode."""
+ GlobalVars._is_normal_test = True
+ GlobalVars._active_cfg = _active_cfg_before_add
+ self.setup_driver(self.setup_config(True, 2))
+ self.driver.add_connection('BRCD_FAB_1', _initiator_target_map)
+ self.assertTrue(_zone_name in GlobalVars._zone_state)
+
+ 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(
+ 'BRCD_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,
+ 'BRCD_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,
+ 'BRCD_FAB_1',
+ _initiator_target_map)
+
+
+class FakeBrcdFCZoneClientCLI(object):
+ def __init__(self, ipaddress, username, password, port):
+ LOG.info(_("User: %s"), username)
+ LOG.info(_("_zone_state: %s"), GlobalVars._zone_state)
+ if not GlobalVars._is_normal_test:
+ raise paramiko.SSHException("Unable to connect to fabric")
+
+ def get_active_zone_set(self):
+ LOG.debug(_("Inside get_active_zone_set %s"), GlobalVars._active_cfg)
+ 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 is_supported_firmware(self):
+ return True
+
+ def get_nameserver_info(self):
+ return _target_ns_map
+
+ def close_connection(self):
+ pass
+
+ def cleanup(self):
+ pass
+
+
+class FakeBrcdFCSanLookupService(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
--- /dev/null
+# (c) Copyright 2013 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 fc san lookup service."""
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume import configuration as conf
+from cinder.zonemanager.fc_san_lookup_service import FCSanLookupService
+
+LOG = logging.getLogger(__name__)
+
+_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.test_brcd_lookup_service.FakeBrcdFCSanLookupService')
+ 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 FakeBrcdFCSanLookupService(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 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 FC Zone Manager."""
+
+import mock
+
+from cinder import exception
+from cinder import test
+from cinder.volume import configuration as conf
+from cinder.zonemanager.drivers.fc_zone_driver import FCZoneDriver
+from cinder.zonemanager.fc_zone_manager import ZoneManager
+from mock import Mock
+
+fabric_name = 'BRCD_FAB_3'
+init_target_map = {'10008c7cff523b01': ['20240002ac000a50']}
+fabric_map = {'BRCD_FAB_3': ['20240002ac000a50']}
+target_list = ['20240002ac000a50']
+
+
+class TestFCZoneManager(ZoneManager, test.TestCase):
+
+ def setUp(self):
+ super(TestFCZoneManager, self).setUp()
+ self.configuration = conf.Configuration(None)
+ self.configuration.set_default('fc_fabric_names', fabric_name)
+ self.driver = Mock(FCZoneDriver)
+
+ def __init__(self, *args, **kwargs):
+ test.TestCase.__init__(self, *args, **kwargs)
+
+ def test_add_connection(self):
+ with mock.patch.object(self.driver, 'add_connection')\
+ as add_connection_mock:
+ self.driver.get_san_context.return_value = fabric_map
+ self.add_connection(init_target_map)
+ self.driver.get_san_context.assert_called_once(target_list)
+ add_connection_mock.assert_called_once_with(fabric_name,
+ init_target_map)
+
+ def test_add_connection_error(self):
+ with mock.patch.object(self.driver, 'add_connection')\
+ as add_connection_mock:
+ add_connection_mock.side_effect = exception.FCZoneDriverException
+ self.assertRaises(exception.ZoneManagerException,
+ self.add_connection, init_target_map)
+
+ def test_delete_connection(self):
+ with mock.patch.object(self.driver, 'delete_connection')\
+ as delete_connection_mock:
+ self.driver.get_san_context.return_value = fabric_map
+ self.delete_connection(init_target_map)
+ self.driver.get_san_context.assert_called_once_with(target_list)
+ delete_connection_mock.assert_called_once_with(fabric_name,
+ init_target_map)
+
+ def test_delete_connection_error(self):
+ with mock.patch.object(self.driver, 'delete_connection')\
+ as del_connection_mock:
+ del_connection_mock.side_effect = exception.FCZoneDriverException
+ self.assertRaises(exception.ZoneManagerException,
+ self.delete_connection, init_target_map)
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 Volume Manager."""
+
+import mock
+
+from cinder import exception
+from cinder import test
+from cinder import utils
+from cinder.volume import configuration as conf
+from cinder.volume.driver import VolumeDriver
+from cinder.volume.manager import VolumeManager
+from cinder.zonemanager.fc_zone_manager import ZoneManager
+from mock import Mock
+from mock import patch
+
+init_target_map = {'10008c7cff523b01': ['20240002ac000a50']}
+conn_info = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {
+ 'target_discovered': True,
+ 'target_lun': 1,
+ 'target_wwn': '20240002ac000a50',
+ 'initiator_target_map': {
+ '10008c7cff523b01': ['20240002ac000a50']
+ }
+ }
+}
+conn_info_no_init_target_map = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {
+ 'target_discovered': True,
+ 'target_lun': 1,
+ 'target_wwn': '20240002ac000a50',
+ }
+}
+
+
+class TestVolumeManager(VolumeManager, test.TestCase):
+
+ def setUp(self):
+ super(TestVolumeManager, self).setUp()
+ self.configuration = conf.Configuration(None)
+ self.configuration.set_default('fc_fabric_names', 'BRCD_FAB_4')
+ self.configuration.zoning_mode = 'fabric'
+ self.driver = Mock(VolumeDriver)
+ self.driver.initialize_connection.return_value = conn_info
+ self.driver.terminate_connection.return_value = conn_info
+ self.driver.create_export.return_value = None
+ self.db = Mock()
+ self.db.volume_get.return_value = {'volume_type_id': None}
+ self.db.volume_admin_metadata_get.return_value = {}
+ self.context_mock = Mock()
+ self.context_mock.elevated.return_value = None
+
+ def __init__(self, *args, **kwargs):
+ test.TestCase.__init__(self, *args, **kwargs)
+
+ @patch.object(utils, 'require_driver_initialized')
+ def test_initialize_connection_voltype_fc_mode_fabric(self,
+ utils_mock):
+ utils_mock.return_value = True
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ self.initialize_connection(self.context_mock, None, None)
+ add_del_conn_mock.assert_called_once_with(conn_info, 1)
+
+ @patch.object(utils, 'require_driver_initialized')
+ def test_initialize_connection_voltype_fc_mode_none(self,
+ utils_mock):
+ utils_mock.return_value = True
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ self.configuration.zoning_mode = 'none'
+ self.initialize_connection(self.context_mock, None, None)
+ assert not add_del_conn_mock.called
+
+ def test_terminate_connection_exception(self):
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ add_del_conn_mock.side_effect = exception.ZoneManagerException
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.terminate_connection, None, None, None,
+ False)
+
+ @patch.object(utils, 'require_driver_initialized')
+ def test_terminate_connection_voltype_fc_mode_fabric(self,
+ utils_mock):
+ utils_mock.return_value = True
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ self.terminate_connection(None, None, None, False)
+ add_del_conn_mock.assert_called_once_with(conn_info, 0)
+
+ @patch.object(utils, 'require_driver_initialized')
+ def test_terminate_connection_mode_none(self,
+ utils_mock):
+ utils_mock.return_value = True
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ self.configuration.zoning_mode = 'none'
+ self.terminate_connection(None, None, None, False)
+ assert not add_del_conn_mock.called
+
+ @patch.object(utils, 'require_driver_initialized')
+ def test_terminate_connection_conn_info_none(self,
+ utils_mock):
+ utils_mock.return_value = True
+ self.driver.terminate_connection.return_value = None
+ with mock.patch.object(VolumeManager, '_add_or_delete_fc_connection')\
+ as add_del_conn_mock:
+ self.terminate_connection(None, None, None, False)
+ assert not add_del_conn_mock.called
+
+ @patch.object(ZoneManager, 'add_connection')
+ def test__add_or_delete_connection_add(self,
+ add_connection_mock):
+ self._add_or_delete_fc_connection(conn_info, 1)
+ add_connection_mock.assert_called_once_with(init_target_map)
+
+ @patch.object(ZoneManager, 'delete_connection')
+ def test__add_or_delete_connection_delete(self,
+ delete_connection_mock):
+ self._add_or_delete_fc_connection(conn_info, 0)
+ delete_connection_mock.assert_called_once_with(init_target_map)
+
+ @patch.object(ZoneManager, 'delete_connection')
+ def test__add_or_delete_connection_no_init_target_map(self,
+ del_conn_mock):
+ self._add_or_delete_fc_connection(conn_info_no_init_target_map, 0)
+ assert not del_conn_mock.called
from cinder.volume import rpcapi as volume_rpcapi
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types
+from cinder.zonemanager.fc_zone_manager import ZoneManager
from eventlet.greenpool import GreenPool
default=False,
help='Offload pending volume delete during '
'volume service startup'),
+ cfg.StrOpt('zoning_mode',
+ default='none',
+ help='FC Zoning mode configured'),
]
CONF = cfg.CONF
if volume_metadata.get('readonly') == 'True'
else 'rw')
conn_info['data']['access_mode'] = access_mode
+ # NOTE(skolathur): If volume_type is fibre_channel, invoke
+ # FCZoneManager to add access control via FC zoning.
+ vol_type = conn_info.get('driver_volume_type', None)
+ mode = self.configuration.zoning_mode
+ LOG.debug(_("Zoning Mode: %s"), mode)
+ if vol_type == 'fibre_channel' and mode == 'fabric':
+ self._add_or_delete_fc_connection(conn_info, 1)
return conn_info
def terminate_connection(self, context, volume_id, connector, force=False):
volume_ref = self.db.volume_get(context, volume_id)
try:
- self.driver.terminate_connection(volume_ref,
- connector, force=force)
+ conn_info = self.driver.terminate_connection(volume_ref,
+ connector,
+ force=force)
+ # NOTE(skolathur): If volume_type is fibre_channel, invoke
+ # FCZoneManager to remove access control via FC zoning.
+ if conn_info:
+ vol_type = conn_info.get('driver_volume_type', None)
+ mode = self.configuration.zoning_mode
+ LOG.debug(_("Zoning Mode: %s"), mode)
+ if vol_type == 'fibre_channel' and mode == 'fabric':
+ self._add_or_delete_fc_connection(conn_info, 0)
except Exception as err:
err_msg = (_('Unable to terminate volume connection: %(err)s')
% {'err': str(err)})
if new_reservations:
QUOTAS.commit(context, new_reservations, project_id=project_id)
self.publish_service_capabilities(context)
+
+ def _add_or_delete_fc_connection(self, conn_info, zone_op):
+ """Add or delete connection control to fibre channel network.
+
+ In case of fibre channel, when zoning mode is set as fabric
+ ZoneManager is invoked to apply FC zoning configuration to the network
+ using initiator and target WWNs used for attach/detach.
+
+ params conn_info: connector passed by volume driver after
+ initialize_connection or terminate_connection.
+ params zone_op: Indicates if it is a zone add or delete operation
+ zone_op=0 for delete connection and 1 for add connection
+ """
+ _initiator_target_map = None
+ if 'initiator_target_map' in conn_info['data']:
+ _initiator_target_map = conn_info['data']['initiator_target_map']
+ LOG.debug(_("Initiator Target map:%s"), _initiator_target_map)
+ # NOTE(skolathur): Invoke Zonemanager to handle automated FC zone
+ # management when vol_type is fibre_channel and zoning_mode is fabric
+ # Initiator_target map associating each initiator WWN to one or more
+ # target WWN is passed to ZoneManager to add or update zone config.
+ LOG.debug(_("Zoning op: %s"), zone_op)
+ if _initiator_target_map is not None:
+ kwargs = {'driver_volume_type': 'fibre_channel',
+ 'configuration': self.configuration}
+ zonemanager = ZoneManager(**kwargs)
+ try:
+ if zone_op == 1:
+ zonemanager.add_connection(_initiator_target_map)
+ elif zone_op == 0:
+ zonemanager.delete_connection(_initiator_target_map)
+ except exception.ZoneManagerException as e:
+ with excutils.save_and_reraise_exception():
+ LOG.error(str(e))
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+
+
+"""
+:mod:`cinder.zonemanager` -- FC Zone manager
+=====================================================
+
+.. automodule:: cinder.zonemanager
+ :platform: Unix
+ :synopsis: Module containing all the FC Zone Manager classes
+"""
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+
+
+"""
+:mod:`cinder.zonemanager.driver` -- FC Zone Drivers
+=====================================================
+
+.. automodule:: cinder.zonemanager.driver
+ :platform: Unix
+ :synopsis: Module containing all the FC Zone drivers.
+"""
--- /dev/null
+# (c) Copyright 2013 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+
+
+"""
+:mod:`cinder.zonemanager.driver.brocade` -- Brocade FC Zone Drivers
+=====================================================
+
+.. automodule:: cinder.zonemanager.driver.brocade
+ :platform: Unix
+ :synopsis: Module containing all the Brocade FC Zone drivers.
+"""
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 paramiko
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import utils
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+from cinder.zonemanager.drivers.fc_common import FCCommon
+
+LOG = logging.getLogger(__name__)
+
+
+CONF = cfg.CONF
+CONF.import_opt('fc_fabric_names', 'cinder.zonemanager.drivers.fc_common')
+
+
+class BrcdFCSanLookupService(FCCommon):
+
+ def __init__(self, **kwargs):
+ """Initializing the client."""
+ super(BrcdFCSanLookupService, self).__init__(**kwargs)
+ self.configuration = kwargs.get('configuration', None)
+ self.create_configuration()
+ self.client = paramiko.SSHClient()
+ self.client.load_system_host_keys()
+ self.client.set_missing_host_key_policy(paramiko.WarningPolicy())
+
+ def create_configuration(self):
+ """Configuration specific to SAN context values."""
+ config = self.configuration
+
+ fc_fabric_opts = []
+ fabric_names = 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.
+ if len(fabric_names) > 0:
+ for fabric_name in fabric_names:
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_address_'
+ + fabric_name,
+ default='',
+ help='Management IP '
+ 'of fabric'))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_user_'
+ + fabric_name,
+ default='',
+ help='Fabric user ID'))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_password_'
+ + fabric_name,
+ default='',
+ help='Password for user',
+ secret=True))
+ fc_fabric_opts.append(cfg.IntOpt('fc_fabric_port_'
+ + fabric_name, default=22,
+ help='Connecting port'))
+ fc_fabric_opts.append(cfg.StrOpt('principal_switch_wwn_'
+ + fabric_name,
+ default=fabric_name,
+ help='Principal switch WWN '
+ 'of the fabric'))
+ config.append_config_values(fc_fabric_opts)
+
+ def get_device_mapping_from_network(self,
+ initiator_wwn_list,
+ target_wwn_list):
+ """Provides the initiator/target map for available SAN contexts.
+
+ Looks up nameserver 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
+ fabrics = None
+ if not fabric_names:
+ raise exception.InvalidParameterValue(
+ err=_("Missing Fibre Channel SAN configuration "
+ "param - fc_fabric_names"))
+
+ fabrics = fabric_names.split(',')
+ LOG.debug(_("FC Fabric List: %s"), fabrics)
+ if fabrics:
+ for t in target_wwn_list:
+ formatted_target_list.append(self.get_formatted_wwn(t))
+
+ for i in initiator_wwn_list:
+ formatted_initiator_list.append(self.
+ get_formatted_wwn(i))
+
+ for fabric_name in fabrics:
+ fabric_ip = self.configuration.safe_get('fc_fabric_address_'
+ + fabric_name)
+ fabric_user = self.configuration.safe_get('fc_fabric_user_'
+ + fabric_name)
+ fabric_pwd = self.configuration.safe_get('fc_fabric_password_'
+ + fabric_name)
+ fabric_port = self.configuration.safe_get(
+ 'fc_fabric_port_' + fabric_name)
+ fabric_principal_wwn = self.configuration.safe_get(
+ 'principal_switch_wwn_'
+ + fabric_name)
+
+ # Get name server data from fabric and find the targets
+ # logged in
+ nsinfo = ''
+ try:
+ LOG.debug(_("Getting name server data for "
+ "fabric %s"), fabric_ip)
+ self.client.connect(
+ fabric_ip, fabric_port, fabric_user, fabric_pwd)
+ nsinfo = self.get_nameserver_info()
+ except exception.FCSanLookupServiceException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Failed collecting name server info from "
+ "fabric %s") % fabric_ip)
+ except Exception as e:
+ msg = _("SSH connection failed "
+ "for %(fabric) with error: %(err)"
+ ) % {'fabric': fabric_ip, 'err': str(e)}
+ LOG.error(msg)
+ raise exception.FCSanLookupServiceException(message=msg)
+ finally:
+ self.close_connection()
+ LOG.debug(_("Lookup service:nsinfo-%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 nameserver for SAN %s"),
+ fabric_name)
+
+ 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 nameserver "
+ "for SAN %s"), fabric_name)
+
+ fabric_map = {
+ 'initiator_port_wwn_list': visible_initiators,
+ 'target_port_wwn_list': visible_targets
+ }
+ device_map[fabric_principal_wwn] = fabric_map
+ LOG.debug(_("Device map for SAN context: %s"), device_map)
+ return device_map
+
+ 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
+ """
+ cli_output = None
+ nsinfo_list = []
+ try:
+ cli_output = self._get_switch_data(ZoneConstant.NS_SHOW)
+ except exception.FCSanLookupServiceException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Failed collecting nsshow info for fabric"))
+ if cli_output:
+ nsinfo_list = self._parse_ns_output(cli_output)
+ try:
+ cli_output = self._get_switch_data(ZoneConstant.NS_CAM_SHOW)
+ except exception.FCSanLookupServiceException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Failed collecting nscamshow"))
+ if cli_output:
+ nsinfo_list.extend(self._parse_ns_output(cli_output))
+ LOG.debug(_("Connector returning nsinfo-%s"), nsinfo_list)
+ return nsinfo_list
+
+ def close_connection(self):
+ """This will close the client connection."""
+ self.client.close()
+ self.client = None
+
+ def _get_switch_data(self, cmd):
+ stdin, stdout, stderr = None, None, None
+ utils.check_ssh_injection([cmd])
+ try:
+ stdin, stdout, stderr = self.client.exec_command(cmd)
+ switch_data = stdout.readlines()
+ except paramiko.SSHException as e:
+ msg = (_("SSH Command failed with error '%(err)r' "
+ "'%(command)s'") % {'err': str(e), 'command': cmd})
+ LOG.error(msg)
+ raise exception.FCSanLookupServiceException(message=msg)
+ finally:
+ if (stdin):
+ stdin.flush()
+ stdin.close()
+ if (stdout):
+ stdout.close()
+ if (stderr):
+ stderr.close()
+ return switch_data
+
+ 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(" NL " in line or " 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 nameserver string: %s") % line
+ LOG.error(msg)
+ raise exception.InvalidParameterValue(err=msg)
+ return nsinfo_list
+
+ def get_formatted_wwn(self, wwn_str):
+ """Utility API that formats WWN to insert ':'."""
+ if (len(wwn_str) != 16):
+ return wwn_str.lower()
+ else:
+ return (':'.join([wwn_str[i:i + 2]
+ for i in range(0, len(wwn_str), 2)])).lower()
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 brocade SAN switches.
+"""
+
+import random
+import re
+
+from eventlet import greenthread
+
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+from cinder import utils
+import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
+
+LOG = logging.getLogger(__name__)
+
+
+class BrcdFCZoneClientCLI(object):
+ switch_ip = None
+ switch_port = '22'
+ switch_user = 'admin'
+ switch_pwd = 'none'
+ patrn = re.compile('[;\s]+')
+
+ def __init__(self, ipaddress, username, password, port):
+ """initializing the client."""
+ self.switch_ip = ipaddress
+ self.switch_port = port
+ self.switch_user = username
+ self.switch_pwd = password
+ 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])
+ except exception.BrocadeZoningCliException:
+ 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:
+ line_split = re.split('\\t', line)
+ if len(line_split) > 2:
+ line_split = [x.replace(
+ '\n', '') for x in line_split]
+ line_split = [x.replace(
+ ' ',
+ '') for x in line_split]
+ if ZoneConstant.CFG_ZONESET in line_split:
+ zone_set_name = line_split[1]
+ continue
+ if line_split[1]:
+ zone_name = line_split[1]
+ zone[zone_name] = list()
+ if line_split[2]:
+ zone_member = line_split[2]
+ if zone_member:
+ 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:
+ # Incase 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)
+ LOG.exception(ex)
+ raise exception.FCZoneDriverException(reason=msg)
+ switch_data = None
+ return zone_set
+
+ def add_zones(self, zones, activate):
+ """Add zone configuration.
+
+ This method will add the zone configuration passed by user.
+ input params:
+ zones - zone names mapped to members.
+ zone members are colon separated but case-insensitive
+ { zonename1:[zonememeber1,zonemember2,...],
+ zonename2:[zonemember1, zonemember2,...]...}
+ e.g: {'openstack50060b0000c26604201900051ee8e329':
+ ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']
+ }
+ activate - True/False
+ """
+ LOG.debug(_("Add Zones - Zones passed: %s"), zones)
+ cfg_name = None
+ iterator_count = 0
+ zone_with_sep = ''
+ active_zone_set = self.get_active_zone_set()
+ LOG.debug(_("Active zone set:%s"), active_zone_set)
+ zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+ LOG.debug(_("zone list:%s"), zone_list)
+ for zone in zones.keys():
+ # if zone exists, its an update. Delete & insert
+ # TODO(skolathur): This can be optimized to an update call later
+ LOG.debug("Update call")
+ if (zone in zone_list):
+ try:
+ self.delete_zones(zone, activate)
+ except exception.BrocadeZoningCliException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Deleting zone failed %s"), zone)
+ LOG.debug(_("Deleted Zone before insert : %s"), zone)
+ zone_members_with_sep = ';'.join(str(member) for
+ member in zones[zone])
+ LOG.debug(_("Forming command for add zone"))
+ cmd = 'zonecreate "%(zone)s", "%(zone_members_with_sep)s"' % {
+ 'zone': zone,
+ 'zone_members_with_sep': zone_members_with_sep}
+ LOG.debug(_("Adding zone, cmd to run %s"), cmd)
+ self.apply_zone_change(cmd.split())
+ LOG.debug(_("Created zones on the switch"))
+ if(iterator_count > 0):
+ zone_with_sep += ';'
+ iterator_count += 1
+ zone_with_sep += zone
+ try:
+ cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]
+ cmd = None
+ if not cfg_name:
+ cfg_name = ZoneConstant.OPENSTACK_CFG_NAME
+ cmd = 'cfgcreate "%(zoneset)s", "%(zones)s"' \
+ % {'zoneset': cfg_name, 'zones': zone_with_sep}
+ else:
+ cmd = 'cfgadd "%(zoneset)s", "%(zones)s"' \
+ % {'zoneset': cfg_name, 'zones': zone_with_sep}
+ LOG.debug(_("New zone %s"), cmd)
+ self.apply_zone_change(cmd.split())
+ self._cfg_save()
+ if activate:
+ self.activate_zoneset(cfg_name)
+ except Exception as e:
+ self._cfg_trans_abort()
+ msg = _("Creating and activating zone set failed: "
+ "(Zone set=%(cfg_name)s error=%(err)s)."
+ ) % {'cfg_name': cfg_name, 'err': str(e)}
+ LOG.error(msg)
+ raise exception.BrocadeZoningCliException(reason=msg)
+
+ def activate_zoneset(self, cfgname):
+ """Method to Activate the zone config. Param cfgname - ZonesetName."""
+ cmd_list = [ZoneConstant.ACTIVATE_ZONESET, cfgname]
+ return self._ssh_execute(cmd_list, True, 1)
+
+ def deactivate_zoneset(self):
+ """Method to deActivate the zone config."""
+ return self._ssh_execute([ZoneConstant.DEACTIVATE_ZONESET], True, 1)
+
+ def delete_zones(self, zone_names, activate):
+ """Delete zones from fabric.
+
+ Method to delete the active zone config zones
+
+ params zone_names: zoneNames separated by semicolon
+ params activate: True/False
+ """
+ active_zoneset_name = None
+ active_zone_set = None
+ zone_list = []
+ active_zone_set = self.get_active_zone_set()
+ active_zoneset_name = active_zone_set[
+ ZoneConstant.ACTIVE_ZONE_CONFIG]
+ zone_list = active_zone_set[ZoneConstant.CFG_ZONES]
+ zones = self.patrn.split(''.join(zone_names))
+ cmd = None
+ try:
+ if len(zones) == len(zone_list):
+ self.deactivate_zoneset()
+ cmd = 'cfgdelete "%(active_zoneset_name)s"' \
+ % {'active_zoneset_name': active_zoneset_name}
+ # Active zoneset is being deleted, hence reset is_active
+ activate = False
+ else:
+ cmd = 'cfgremove "%(active_zoneset_name)s", "%(zone_names)s"' \
+ % {'active_zoneset_name': active_zoneset_name,
+ 'zone_names': zone_names
+ }
+ LOG.debug(_("Delete zones: Config cmd to run:%s"), cmd)
+ self.apply_zone_change(cmd.split())
+ for zone in zones:
+ self._zone_delete(zone)
+ self._cfg_save()
+ if activate:
+ self.activate_zoneset(active_zoneset_name)
+ except Exception as e:
+ msg = _("Deleting zones failed: (command=%(cmd)s error=%(err)s)."
+ ) % {'cmd': cmd, 'err': str(e)}
+ LOG.error(msg)
+ self._cfg_trans_abort()
+ raise exception.BrocadeZoningCliException(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
+ """
+ cli_output = None
+ return_list = []
+ try:
+ cli_output = self._get_switch_info([ZoneConstant.NS_SHOW])
+ except exception.BrocadeZoningCliException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Failed collecting nsshow "
+ "info for fabric %s"), self.switch_ip)
+ if (cli_output):
+ return_list = self._parse_ns_output(cli_output)
+ try:
+ cli_output = self._get_switch_info([ZoneConstant.NS_CAM_SHOW])
+ except exception.BrocadeZoningCliException:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Failed collecting nscamshow "
+ "info for fabric %s"), self.switch_ip)
+ if (cli_output):
+ return_list.extend(self._parse_ns_output(cli_output))
+ cli_output = None
+ return return_list
+
+ def _cfg_save(self):
+ self._ssh_execute([ZoneConstant.CFG_SAVE], True, 1)
+
+ def _zone_delete(self, zone_name):
+ cmd = 'zonedelete "%(zone_name)s"' % {'zone_name': zone_name}
+ self.apply_zone_change(cmd.split())
+
+ def _cfg_trans_abort(self):
+ is_abortable = self._is_trans_abortable()
+ if(is_abortable):
+ self.apply_zone_change([ZoneConstant.CFG_ZONE_TRANS_ABORT])
+
+ def _is_trans_abortable(self):
+ is_abortable = False
+ stdout, stderr = None, None
+ stdout, stderr = self._run_ssh(
+ [ZoneConstant.CFG_SHOW_TRANS], True, 1)
+ output = stdout.splitlines()
+ is_abortable = False
+ for line in output:
+ if(ZoneConstant.TRANS_ABORTABLE in line):
+ is_abortable = True
+ break
+ if stderr:
+ msg = _("Error while checking transaction status: %s") % stderr
+ raise exception.BrocadeZoningCliException(reason=msg)
+ else:
+ return is_abortable
+
+ def apply_zone_change(self, cmd_list):
+ """Execute zoning cli with no status update.
+
+ Executes CLI commands such as addZone where status return is
+ not expected.
+ """
+ stdout, stderr = None, None
+ LOG.debug(_("Executing command via ssh: %s"), cmd_list)
+ stdout, stderr = self._run_ssh(cmd_list, True, 1)
+ # no output expected, so output means there is an error
+ if stdout:
+ msg = _("Error while running zoning CLI: (command=%(cmd)s "
+ "error=%(err)s).") % {'cmd': cmd_list, 'err': stdout}
+ LOG.error(msg)
+ self._cfg_trans_abort()
+ raise exception.BrocadeZoningCliException(reason=msg)
+
+ def is_supported_firmware(self):
+ """Check firmware version is v6.4 or higher.
+
+ This API checks if the firmware version per the plug-in support level.
+ This only checks major and minor version.
+ """
+ cmd = ['version']
+ firmware = 0
+ try:
+ stdout, stderr = self._execute_shell_cmd(cmd)
+ if (stdout):
+ for line in stdout:
+ if 'Fabric OS: v' in line:
+ LOG.debug(_("Firmware version string:%s"), line)
+ ver = line.split('Fabric OS: v')[1].split('.')
+ if (ver):
+ firmware = int(ver[0] + ver[1])
+ return firmware > 63
+ else:
+ LOG.error(_("No CLI output for firmware version check"))
+ return False
+ except processutils.ProcessExecutionError as e:
+ msg = _("Error while getting data via ssh: (command=%(cmd)s "
+ "error=%(err)s).") % {'cmd': cmd, 'err': str(e)}
+ LOG.error(msg)
+ raise exception.BrocadeZoningCliException(reason=msg)
+
+ def _get_switch_info(self, cmd_list):
+ stdout, stderr, sw_data = None, None, None
+ try:
+ stdout, stderr = self._run_ssh(cmd_list, True, 1)
+ 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': str(e)}
+ LOG.error(msg)
+ raise exception.BrocadeZoningCliException(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(" NL " in line or " 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 nameserver 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):
+ # TODO(skolathur): Need to implement ssh_injection check
+ # currently, the check will fail for zonecreate command
+ # as zone members are separated by ';'which is a danger char
+ command = ' '. join(cmd_list)
+
+ if not self.sshpool:
+ self.sshpool = 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:
+ LOG.error(e)
+ 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 such as cfgsave where status return is expected.
+ """
+ utils.check_ssh_injection(cmd_list)
+ command = ' '. join(cmd_list)
+
+ if not self.sshpool:
+ self.sshpool = 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)
+ stdin.write("%s\n" % ZoneConstant.YES)
+ 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.error(e)
+ last_exception = e
+ greenthread.sleep(random.randint(20, 500) / 100.0)
+ LOG.debug(_("Handling error case after "
+ "SSH:%s"), str(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():
+ LOG.error(_("Error executing command via ssh: %s"), str(e))
+ finally:
+ if stdin:
+ stdin.flush()
+ stdin.close()
+ if stdout:
+ stdout.close()
+ if stderr:
+ stderr.close()
+
+ def _execute_shell_cmd(self, cmd):
+ """Run command over shell for older firmware versions.
+
+ We invoke shell and issue the command and return the output.
+ This is primarily used for issuing read commands when we are not sure
+ if the firmware supports exec_command.
+ """
+ utils.check_ssh_injection(cmd)
+ command = ' '. join(cmd)
+ stdout, stderr = None, None
+ if not self.sshpool:
+ self.sshpool = utils.SSHPool(self.switch_ip,
+ self.switch_port,
+ None,
+ self.switch_user,
+ self.switch_pwd,
+ min_size=1,
+ max_size=5)
+ with self.sshpool.item() as ssh:
+ LOG.debug('Running cmd (SSH): %s' % command)
+ channel = ssh.invoke_shell()
+ stdin_stream = channel.makefile('wb')
+ stdout_stream = channel.makefile('rb')
+ stderr_stream = channel.makefile('rb')
+ stdin_stream.write('''%s
+exit
+''' % command)
+ stdin_stream.flush()
+ stdout = stdout_stream.readlines()
+ stderr = stderr_stream.readlines()
+ stdin_stream.close()
+ stdout_stream.close()
+ stderr_stream.close()
+
+ exit_status = channel.recv_exit_status()
+ # exit_status == -1 if no exit code was returned
+ if exit_status != -1:
+ LOG.debug('Result was %s' % exit_status)
+ if exit_status != 0:
+ msg = "command %s failed" % command
+ LOG.debug(msg)
+ raise processutils.ProcessExecutionError(
+ exit_code=exit_status,
+ stdout=stdout,
+ stderr=stderr,
+ cmd=command)
+ try:
+ channel.close()
+ except Exception as e:
+ LOG.exception(e)
+ LOG.debug("_execute_cmd: stdout to return:%s" % stdout)
+ LOG.debug("_execute_cmd: stderr to return:%s" % stderr)
+ return (stdout, stderr)
+
+ def cleanup(self):
+ self.sshpool = None
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+"""
+Brocade Zone Driver is responsible to manage access control using FC zoning
+for Brocade 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
+
+from cinder import exception
+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.fc_zone_driver import FCZoneDriver
+
+LOG = logging.getLogger(__name__)
+
+brcd_opts = [
+ cfg.StrOpt('brcd_sb_connector',
+ default='cinder.zonemanager.drivers.brocade'
+ '.brcd_fc_zone_client_cli.BrcdFCZoneClientCLI',
+ help='Southbound connector for zoning operation'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(brcd_opts)
+CONF.import_opt('zone_activate', 'cinder.zonemanager.drivers.fc_zone_driver')
+CONF.import_opt('zone_name_prefix',
+ 'cinder.zonemanager.drivers.fc_zone_driver')
+CONF.import_opt('fc_fabric_names', 'cinder.zonemanager.drivers.fc_common')
+
+
+class BrcdFCZoneDriver(FCZoneDriver):
+ """Brocade FC zone driver implementation.
+
+ OpenStack Fibre Channel zone driver to manage FC zoning in
+ Brocade SAN fabrics.
+
+ Version history:
+ 1.0 - Initial Brocade FC zone driver
+ """
+
+ def __init__(self, **kwargs):
+ super(BrcdFCZoneDriver, self).__init__(**kwargs)
+ self.configuration = kwargs.get('configuration', None)
+ if self.configuration:
+ self.configuration.append_config_values(brcd_opts)
+ # Adding a hack to hendle parameters from super classes
+ # in case configured with multi backend.
+ fabric_names = self.configuration.safe_get('fc_fabric_names')
+ activate = self.configuration.safe_get('zone_activate')
+ prefix = self.configuration.safe_get('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('zone_activate',
+ default=True,
+ help='Indicates whether zone should '
+ 'be activated or not'))
+ if not prefix:
+ base_san_opts.append(
+ cfg.StrOpt('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 = self.configuration.fc_fabric_names.split(',')
+ fc_fabric_opts = []
+ # There can be more than one SAN in the network and we need to
+ # get credentials for each SAN.
+ if fabric_names:
+ for fabric_name in fabric_names:
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_address_'
+ + fabric_name,
+ default='',
+ help='Management IP'
+ ' of fabric'))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_user_'
+ + fabric_name,
+ default='',
+ help='Fabric user ID'))
+ fc_fabric_opts.append(cfg.StrOpt('fc_fabric_password_'
+ + fabric_name,
+ default='',
+ help='Password for user',
+ secret=True))
+ fc_fabric_opts.append(cfg.IntOpt('fc_fabric_port_'
+ + fabric_name,
+ default=22,
+ help='Connecting port'))
+ fc_fabric_opts.append(cfg.StrOpt('zoning_policy_'
+ + fabric_name,
+ default=self.configuration
+ .zoning_policy,
+ help='overridden '
+ 'zoning policy'))
+ fc_fabric_opts.append(cfg.BoolOpt('zone_activate_'
+ + fabric_name,
+ default=self
+ .configuration
+ .zone_activate,
+ help='overridden zoning '
+ 'activation state'))
+ fc_fabric_opts.append(cfg.StrOpt('zone_name_prefix_'
+ + fabric_name,
+ default=self.configuration
+ .zone_name_prefix,
+ help='overridden zone '
+ 'name prefix'))
+ fc_fabric_opts.append(cfg.StrOpt('principal_switch_wwn_'
+ + fabric_name,
+ default=fabric_name,
+ help='Principal switch '
+ 'WWN of the fabric'))
+ self.configuration.append_config_values(fc_fabric_opts)
+
+ def get_formatted_wwn(self, wwn_str):
+ """Utility API that formats WWN to insert ':'."""
+ wwn_str = wwn_str.encode('ascii')
+ if len(wwn_str) != 16:
+ return wwn_str
+ else:
+ return ':'.join(
+ [wwn_str[i:i + 2] for i in range(0, len(wwn_str), 2)])
+
+ @lockutils.synchronized('brcd', 'fcfabric-', True)
+ def add_connection(self, fabric, initiator_target_map):
+ """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(_("BrcdFCZoneDriver - Add connection "
+ "for I-T map: %s"), initiator_target_map)
+ fabric_ip = self.configuration.safe_get(
+ 'fc_fabric_address_' + fabric)
+ fabric_user = self.configuration.safe_get(
+ 'fc_fabric_user_' + fabric)
+ fabric_pwd = self.configuration.safe_get(
+ 'fc_fabric_password_' + fabric)
+ fabric_port = self.configuration.safe_get(
+ 'fc_fabric_port_' + fabric)
+ zoning_policy = self.configuration.zoning_policy
+ zoning_policy_fab = self.configuration.safe_get(
+ 'zoning_policy_' + fabric)
+ if zoning_policy_fab:
+ zoning_policy = zoning_policy_fab
+
+ LOG.info(_("Zoning policy for Fabric %s"), zoning_policy)
+ cli_client = None
+ try:
+ cli_client = importutils.import_object(
+ self.configuration.brcd_sb_connector,
+ ipaddress=fabric_ip,
+ username=fabric_user,
+ password=fabric_pwd,
+ port=fabric_port)
+ if not cli_client.is_supported_firmware():
+ msg = _("Unsupported firmware on switch %s. Make sure "
+ "switch is running firmware v6.4 or higher"
+ ) % fabric_ip
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ except exception.BrocadeZoningCliException as brocade_ex:
+ raise exception.FCZoneDriverException(str(brocade_ex))
+ except Exception as e:
+ LOG.error(str(e))
+ msg = _("Failed to add zoning configuration %s"
+ ) % str(e)
+ raise exception.FCZoneDriverException(msg)
+
+ cfgmap_from_fabric = self.get_active_zone_set(
+ fabric_ip, fabric_user, fabric_pwd, fabric_port)
+ 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 = [self.get_formatted_wwn(initiator),
+ self.get_formatted_wwn(target)]
+ zone_name = (self.configuration.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 already exists.
+ LOG.info(_("Zone exists in I-T mode. "
+ "Skipping zone creation %s"), zone_name)
+ elif zoning_policy == 'initiator':
+ zone_members = [self.get_formatted_wwn(initiator)]
+ for t in t_list:
+ target = t.lower()
+ zone_members.append(self.get_formatted_wwn(target))
+
+ zone_name = self.configuration.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:
+ try:
+ cli_client.add_zones(
+ zone_map, self.configuration.zone_activate)
+ cli_client.cleanup()
+ except exception.BrocadeZoningCliException as brocade_ex:
+ raise exception.FCZoneDriverException(str(brocade_ex))
+ except Exception as e:
+ LOG.error(str(e))
+ msg = _("Failed to add zoning configuration %s"
+ ) % str(e)
+ raise exception.FCZoneDriverException(msg)
+ LOG.debug(_("Zones added successfully: %s"), zone_map)
+
+ @lockutils.synchronized('brcd', '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(_("BrcdFCZoneDriver - Delete connection for I-T map: %s"),
+ initiator_target_map)
+ fabric_ip = self.configuration.safe_get(
+ 'fc_fabric_address_' + fabric)
+ fabric_user = self.configuration.safe_get(
+ 'fc_fabric_user_' + fabric)
+ fabric_pwd = self.configuration.safe_get(
+ 'fc_fabric_password_' + fabric)
+ fabric_port = self.configuration.safe_get(
+ 'fc_fabric_port_' + fabric)
+ zoning_policy = self.configuration.zoning_policy
+ zoning_policy_fab = self.configuration.safe_get(
+ 'zoning_policy_' + fabric)
+ if zoning_policy_fab:
+ zoning_policy = zoning_policy_fab
+
+ LOG.info(_("Zoning policy for fabric %s"), zoning_policy)
+ conn = None
+ try:
+ conn = importutils.import_object(
+ self.configuration.brcd_sb_connector,
+ ipaddress=fabric_ip,
+ username=fabric_user,
+ password=fabric_pwd,
+ port=fabric_port)
+ if not conn.is_supported_firmware():
+ msg = _("Unsupported firmware on switch %s. Make sure "
+ "switch is running firmware v6.4 or higher"
+ ) % fabric_ip
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ except exception.BrocadeZoningCliException as brocade_ex:
+ raise exception.FCZoneDriverException(str(brocade_ex))
+ except Exception as e:
+ LOG.error(str(e))
+ msg = _("Failed to delete zoning configuration %s"
+ ) % str(e)
+ raise exception.FCZoneDriverException(msg)
+
+ cfgmap_from_fabric = self.get_active_zone_set(
+ fabric_ip, fabric_user, fabric_pwd, fabric_port)
+ 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 = self.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.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(self.get_formatted_wwn(target))
+
+ zone_name = self.configuration.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)
+ try:
+ # Update zone membership.
+ if zone_map:
+ conn.add_zones(
+ zone_map, self.configuration.zone_activate)
+ # 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.zone_activate)
+ conn.cleanup()
+ except Exception as e:
+ LOG.error(str(e))
+ msg = _("Failed to update or delete zoning configuration")
+ raise exception.FCZoneDriverException(msg)
+
+ 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.
+ """
+ # TODO(Santhosh Kolathur): consider refactoring to use lookup service.
+ formatted_target_list = []
+ fabric_map = {}
+ fabrics = 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(self.get_formatted_wwn(t.lower()))
+ LOG.debug(_("Formatted Target wwn List:"
+ " %s"), formatted_target_list)
+ for fabric_name in fabrics:
+ fabric_ip = self.configuration.safe_get(
+ 'fc_fabric_address_' + fabric_name)
+ fabric_user = self.configuration.safe_get(
+ 'fc_fabric_user_' + fabric_name)
+ fabric_pwd = self.configuration.safe_get(
+ 'fc_fabric_password_' + fabric_name)
+ fabric_port = self.configuration.safe_get(
+ 'fc_fabric_port_' + fabric_name)
+ conn = None
+ try:
+ conn = importutils.import_object(
+ self.configuration.brcd_sb_connector,
+ ipaddress=fabric_ip,
+ username=fabric_user,
+ password=fabric_pwd,
+ port=fabric_port)
+ if not conn.is_supported_firmware():
+ msg = _("Unsupported firmware on switch %s. Make sure "
+ "switch is running firmware v6.4 or higher"
+ ) % fabric_ip
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ except exception.BrocadeZoningCliException as brocade_ex:
+ raise exception.FCZoneDriverException(str(brocade_ex))
+ except Exception as e:
+ LOG.error(str(e))
+ msg = _("Failed to get SAN context %s"
+ ) % str(e)
+ raise exception.FCZoneDriverException(msg)
+
+ # Get name server data from fabric and get the targets
+ # logged in.
+ nsinfo = None
+ try:
+ nsinfo = conn.get_nameserver_info()
+ LOG.debug(_("name server info from fabric:%s"), nsinfo)
+ conn.cleanup()
+ except exception.BrocadeZoningCliException as ex:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_("Error getting name server "
+ "info: %s"), str(ex))
+ except Exception as e:
+ msg = _("Failed to get name server info:%s") % str(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] = str(
+ visible_targets[idx]).replace(':', '')
+ fabric_map[fabric_name] = visible_targets
+ else:
+ LOG.debug(_("No targets are in the nameserver 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):
+ """Gets active zone config from fabric."""
+ cfgmap = {}
+ conn = None
+ try:
+ LOG.debug(_("Southbound connector:"
+ " %s"), self.configuration.brcd_sb_connector)
+ conn = importutils.import_object(
+ self.configuration.brcd_sb_connector,
+ ipaddress=fabric_ip, username=fabric_user,
+ password=fabric_pwd, port=fabric_port)
+ if not conn.is_supported_firmware():
+ msg = _("Unsupported firmware on switch %s. Make sure "
+ "switch is running firmware v6.4 or higher"
+ ) % fabric_ip
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ cfgmap = conn.get_active_zone_set()
+ conn.cleanup()
+ except exception.BrocadeZoningCliException as brocade_ex:
+ raise exception.FCZoneDriverException(str(brocade_ex))
+ except Exception as e:
+ msg = _("Failed to access active zoning configuration:%s") % str(e)
+ LOG.error(msg)
+ raise exception.FCZoneDriverException(msg)
+ LOG.debug(_("Active zone set from fabric: %s"), cfgmap)
+ return cfgmap
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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 Brocade FC Zone Driver.
+"""
+YES = 'y'
+ACTIVE_ZONE_CONFIG = 'active_zone_config'
+CFG_ZONESET = 'cfg:'
+CFG_ZONES = 'zones'
+OPENSTACK_CFG_NAME = 'OpenStack_Cfg'
+SUCCESS = 'Success'
+TRANS_ABORTABLE = 'It is abortable'
+
+"""
+CLI Commands for FC zoning operations.
+"""
+GET_ACTIVE_ZONE_CFG = 'cfgactvshow'
+ZONE_CREATE = 'zonecreate '
+ZONESET_CREATE = 'cfgcreate '
+CFG_SAVE = 'cfgsave'
+CFG_ADD = 'cfgadd '
+ACTIVATE_ZONESET = 'cfgenable '
+DEACTIVATE_ZONESET = 'cfgdisable'
+CFG_DELETE = 'cfgdelete '
+CFG_REMOVE = 'cfgremove '
+ZONE_DELETE = 'zonedelete '
+CFG_SHOW_TRANS = 'cfgtransshow'
+CFG_ZONE_TRANS_ABORT = 'cfgtransabort'
+NS_SHOW = 'nsshow'
+NS_CAM_SHOW = 'nscamshow'
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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
+
+san_context_opts = [
+ 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'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(san_context_opts)
+
+
+class FCCommon(object):
+ """Common interface for FC operations."""
+
+ def __init__(self, **kwargs):
+ pass
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+
+"""
+Base Zone Driver is responsible to manage access control using FC zoning
+Vendor specific implementations should extend this class to provide
+concrete implementation for add_connection and delete_connection
+interfaces.
+
+**Related Flags**
+
+:zoning_policy: Used by: class: 'FCZoneDriver'. Defaults to 'none'
+:zone_driver: Used by: class: 'FCZoneDriver'. Defaults to 'none'
+
+"""
+
+
+from oslo.config import cfg
+
+from cinder.openstack.common import log as logging
+from cinder.zonemanager.drivers.fc_common import FCCommon
+
+LOG = logging.getLogger(__name__)
+
+fc_zone_opts = [
+ cfg.BoolOpt('zone_activate',
+ default=True,
+ help="Indicates whether zone should be activated or not"),
+ cfg.StrOpt('zone_name_prefix',
+ default="openstack",
+ help="A prefix to be used when naming zone"),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(fc_zone_opts)
+
+
+class FCZoneDriver(FCCommon):
+ """Interface to manage Connection control during attach/detach."""
+
+ def __init__(self, **kwargs):
+ super(FCZoneDriver, self).__init__(**kwargs)
+ LOG.debug(_("Initializing FCZoneDriver"))
+
+ def add_connection(self, fabric, initiator_target_map):
+ """Add connection control.
+
+ Abstract method to add connection control.
+ All implementing drivers should provide concrete implementation
+ for this API.
+ :param fabric: Fabric name from cinder.conf file
+ :param initiator_target_map: Mapping of initiator to list of targets
+ Example initiator_target_map:
+ {
+ '10008c7cff523b01': ['20240002ac000a50', '20240002ac000a40']
+ }
+ Note that WWPN can be in lower or upper case and can be
+ ':' separated strings
+ """
+ raise NotImplementedError()
+
+ def delete_connection(self, fabric, initiator_target_map):
+ """Delete connection control.
+
+ Abstract method to remove connection control.
+ All implementing drivers should provide concrete implementation
+ for this API.
+ :param fabric: Fabric name from cinder.conf file
+ :param initiator_target_map: Mapping of initiator to list of targets
+ Example initiator_target_map:
+ {
+ '10008c7cff523b01': ['20240002ac000a50', '20240002ac000a40']
+ }
+ Note that WWPN can be in lower or upper case and can be
+ ':' separated strings
+ """
+ raise NotImplementedError()
+
+ def get_san_context(self, target_wwn_list):
+ """Get SAN context for end devices.
+
+ Abstract method to get SAN contexts for given list of end devices
+ All implementing drivers should provide concrete implementation
+ for this API.
+ :param fabric: Fabric name from cinder.conf file
+ :param initiator_target_map: Mapping of initiator to list of targets
+ Example initiator_target_map: ['20240002ac000a50', '20240002ac000a40']
+ Note that WWPN can be in lower or upper case and can be
+ ':' separated strings
+ """
+ raise NotImplementedError()
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+"""
+Base Lookup Service for name server lookup to find the initiator to target port
+mapping for available SAN contexts.
+Vendor specific lookup classes are expected to implement the interfaces
+defined in this class.
+"""
+
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import importutils
+from cinder.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+lookup_service_opts = [
+ cfg.StrOpt('fc_san_lookup_service',
+ default='cinder.zonemanager.drivers.brocade'
+ '.brcd_fc_san_lookup_service.BrcdFCSanLookupService',
+ help='FC San Lookup Service'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(lookup_service_opts)
+
+
+class FCSanLookupService(object):
+ """Base Lookup Service.
+
+ Base Lookup Service for name server lookup to find the initiator to
+ target port mapping for available SAN contexts.
+ """
+
+ lookup_service = None
+
+ def __init__(self, **kwargs):
+ self.configuration = kwargs.get('configuration', None)
+ if self.configuration:
+ self.configuration.append_config_values(lookup_service_opts)
+
+ def get_device_mapping_from_network(self, initiator_list, target_list):
+ """Get device mapping from FC network.
+
+ Gets a filtered list of initiator ports and target ports for each SAN
+ available.
+ :param initiator_list list of initiator port WWN
+ :param target_list list of target port WWN
+ :return device wwn map in following format
+ {
+ <San name>: {
+ 'initiator_port_wwn_list':
+ ('200000051E55A100', '200000051E55A121'..)
+ 'target_port_wwn_list':
+ ('100000051E55A100', '100000051E55A121'..)
+ }
+ }
+ :raise Exception when a lookup service implementation is not specified
+ in cinder.conf:fc_san_lookup_service
+ """
+ # Initialize vendor specific implementation of FCZoneDriver
+ if (self.configuration.fc_san_lookup_service):
+ lookup_service = self.configuration.fc_san_lookup_service
+ LOG.debug(_("Lookup service to invoke: "
+ "%s"), lookup_service)
+ self.lookup_service = importutils.import_object(
+ lookup_service, configuration=self.configuration)
+ else:
+ msg = _("Lookup service not configured. Config option for "
+ "fc_san_lookup_service need to specify a concrete "
+ "implementation of lookup service")
+ LOG.error(msg)
+ raise exception.FCSanLookupServiceException(msg)
+ try:
+ device_map = self.lookup_service.get_device_mapping_from_network(
+ initiator_list, target_list)
+ except Exception as e:
+ LOG.error(str(e))
+ raise exception.FCSanLookupServiceException(str(e))
+ return device_map
--- /dev/null
+# (c) Copyright 2014 Brocade Communications Systems Inc.
+# All Rights Reserved.
+#
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# 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.
+#
+"""
+ZoneManager is responsible to manage access control using FC zoning
+when zoning mode is set as 'fabric'.
+ZoneManager provides interfaces to add connection and remove connection
+for given initiator and target list associated with a FC volume attach and
+detach operation.
+
+**Related Flags**
+
+:zone_driver: Used by:class:`ZoneManager`.
+ Defaults to
+ `cinder.zonemanager.drivers.brocade.brcd_fc_zone_driver.BrcdFCZoneDriver`
+:zoning_policy: Used by: class: 'ZoneManager'. Defaults to 'none'
+
+"""
+
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import importutils
+from cinder.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+zone_manager_opts = [
+ cfg.StrOpt('zone_driver',
+ default='cinder.zonemanager.drivers.brocade.brcd_fc_zone_driver'
+ '.BrcdFCZoneDriver',
+ help='FC Zone Driver responsible for zone management'),
+ cfg.StrOpt('zoning_policy',
+ default='initiator-target',
+ help='Zoning policy configured by user'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(zone_manager_opts)
+
+
+class ZoneManager:
+ """Manages Connection control during attach/detach."""
+ driver = None
+ fabric_names = []
+
+ def __init__(self, **kwargs):
+ """Load the driver from the one specified in args, or from flags."""
+
+ self.configuration = kwargs.get('configuration', None)
+ if self.configuration:
+ self.configuration.append_config_values(zone_manager_opts)
+
+ zone_driver = self.configuration.zone_driver
+ LOG.debug(_("Zone Driver from config: {%s}"), zone_driver)
+
+ # Initialize vendor specific implementation of FCZoneDriver
+ self.driver = importutils.import_object(
+ zone_driver,
+ configuration=self.configuration)
+
+ def get_zoning_state_ref_count(self, initiator_wwn, target_wwn):
+ """Zone management state check.
+
+ Performs state check for given I-T pair to return the current count of
+ active attach for the pair.
+ """
+ # TODO(sk): ref count state management
+ count = 0
+ # check the state for I-T pair
+ return count
+
+ def add_connection(self, initiator_target_map):
+ """Add connection control.
+
+ Adds connection control for the given initiator target map.
+ initiator_target_map - each initiator WWN mapped to a list of one
+ or more target WWN:
+ eg:
+ {
+ '10008c7cff523b01': ['20240002ac000a50', '20240002ac000a40']
+ }
+ """
+ connected_fabric = None
+ try:
+ for initiator in initiator_target_map.keys():
+ target_list = initiator_target_map[initiator]
+ LOG.debug(_("Target List :%s"), {initiator: target_list})
+
+ # get SAN context for the target list
+ fabric_map = self.driver.get_san_context(target_list)
+ LOG.debug(_("Fabric Map after context lookup:%s"), fabric_map)
+ # iterate over each SAN and apply connection control
+ for fabric in fabric_map.keys():
+ connected_fabric = fabric
+ t_list = fabric_map[fabric]
+ # get valid I-T map to add connection control
+ i_t_map = {initiator: t_list}
+ valid_i_t_map = self.get_valid_initiator_target_map(
+ i_t_map, True)
+ LOG.info(_("Final filtered map for fabric: %s"),
+ {fabric: valid_i_t_map})
+
+ # Call driver to add connection control
+ self.driver.add_connection(fabric, valid_i_t_map)
+
+ LOG.info(_("Add Connection: Finished iterating "
+ "over all target list"))
+ except Exception as e:
+ msg = _("Failed adding connection for fabric=%(fabric)s: "
+ "Error:%(err)s") % {'fabric': connected_fabric,
+ 'err': str(e)}
+ LOG.error(msg)
+ raise exception.ZoneManagerException(reason=msg)
+
+ def delete_connection(self, initiator_target_map):
+ """Delete connection.
+
+ Updates/deletes connection control for the given initiator target map.
+ initiator_target_map - each initiator WWN mapped to a list of one
+ or more target WWN:
+ eg:
+ {
+ '10008c7cff523b01': ['20240002ac000a50', '20240002ac000a40']
+ }
+ """
+ connected_fabric = None
+ try:
+ for initiator in initiator_target_map.keys():
+ target_list = initiator_target_map[initiator]
+ LOG.info(_("Delete connection Target List:%s"),
+ {initiator: target_list})
+
+ # get SAN context for the target list
+ fabric_map = self.driver.get_san_context(target_list)
+ LOG.debug(_("Delete connection Fabric Map from SAN "
+ "context: %s"), fabric_map)
+
+ # iterate over each SAN and apply connection control
+ for fabric in fabric_map.keys():
+ connected_fabric = fabric
+ t_list = fabric_map[fabric]
+ # get valid I-T map to add connection control
+ i_t_map = {initiator: t_list}
+ valid_i_t_map = self.get_valid_initiator_target_map(
+ i_t_map, False)
+ LOG.info(_("Final filtered map for delete "
+ "connection: %s"), valid_i_t_map)
+
+ # Call driver to delete connection control
+ if len(valid_i_t_map) > 0:
+ self.driver.delete_connection(fabric, valid_i_t_map)
+
+ LOG.debug(_("Delete Connection - Finished iterating over all"
+ " target list"))
+ except Exception as e:
+ msg = _("Failed removing connection for fabric=%(fabric)s: "
+ "Error:%(err)s") % {'fabric': connected_fabric,
+ 'err': str(e)}
+ LOG.error(msg)
+ raise exception.ZoneManagerException(reason=msg)
+
+ def get_valid_initiator_target_map(self, initiator_target_map,
+ add_control):
+ """Reference count check for end devices.
+
+ Looks up the reference count for each initiator-target pair from the
+ map and returns a filtered list based on the operation type
+ add_control - operation type can be true for add connection control
+ and false for remove connection control
+ """
+ filtered_i_t_map = {}
+ for initiator in initiator_target_map.keys():
+ t_list = initiator_target_map[initiator]
+ for target in t_list:
+ count = self.get_zoning_state_ref_count(initiator, target)
+ if add_control:
+ if count > 0:
+ t_list.remove(target)
+ # update count = count + 1
+ else:
+ if count > 1:
+ t_list.remove(target)
+ # update count = count - 1
+ if t_list:
+ filtered_i_t_map[initiator] = t_list
+ else:
+ LOG.info(_("No targets to add or remove connection for "
+ "I: %s"), initiator)
+ return filtered_i_t_map
# (boolean value)
#volume_service_inithost_offload=false
+# FC Zoning mode configured (string value)
+#zoning_mode=none
+
+
+#
+# Options defined in cinder.zonemanager.drivers.brocade.brcd_fc_zone_driver
+#
+
+# Southbound connector for zoning operation (string value)
+#brcd_sb_connector=cinder.zonemanager.drivers.brocade.brcd_fc_zone_client_cli.BrcdFCZoneClientCLI
+
+
+#
+# Options defined in cinder.zonemanager.drivers.fc_common
+#
+
+# 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 (string value)
+#fc_fabric_names=<None>
+
+
+#
+# Options defined in cinder.zonemanager.drivers.fc_zone_driver
+#
+
+# Indicates whether zone should be activated or not (boolean
+# value)
+#zone_activate=true
+
+# A prefix to be used when naming zone (string value)
+#zone_name_prefix=openstack
+
+
+#
+# Options defined in cinder.zonemanager.fc_san_lookup_service
+#
+
+# FC San Lookup Service (string value)
+#fc_san_lookup_service=cinder.zonemanager.drivers.brocade.brcd_fc_san_lookup_service.BrcdFCSanLookupService
+
+
+#
+# Options defined in cinder.zonemanager.fc_zone_manager
+#
+
+# FC Zone Driver responsible for zone management (string
+# value)
+#zone_driver=cinder.zonemanager.drivers.brocade.brcd_fc_zone_driver.BrcdFCZoneDriver
+
+# Zoning policy configured by user (string value)
+#zoning_policy=initiator-target
+
[ssl]