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