From: Edwin Wang Date: Sat, 13 Dec 2014 20:42:09 +0000 (+0800) Subject: Add iSCSI protocol support for IBM FlashSystem X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=8d400f260984bc71c4b436f748e4266a88ae05c6;p=openstack-build%2Fcinder-build.git Add iSCSI protocol support for IBM FlashSystem This change implements iSCSI protocol for Cinder driver. It is inherited from driver.ISCSIDriver and flashsystem.py. We will separate flashsystem.py into common and FC protocol. iSCSI test result: https://bugs.launchpad.net/cinder/+bug/1406442 Implements: blueprint ibm-flashsystem-driver-iscsi Change-Id: Ie6cc2315e9d770302fcdfb63873da1b0dea12bda --- diff --git a/cinder/tests/unit/test_ibm_flashsystem_iscsi.py b/cinder/tests/unit/test_ibm_flashsystem_iscsi.py new file mode 100644 index 000000000..2971b1798 --- /dev/null +++ b/cinder/tests/unit/test_ibm_flashsystem_iscsi.py @@ -0,0 +1,283 @@ +# Copyright 2015 IBM Corp. +# 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. +# + +""" +Tests for the IBM FlashSystem iSCSI volume driver. +""" + +import mock +from oslo_concurrency import processutils +from oslo_log import log as logging +from oslo_utils import excutils +import six + +import random + +from cinder import context +from cinder import exception +from cinder import test +from cinder.tests.unit import test_ibm_flashsystem as fscommon +from cinder import utils +from cinder.volume import configuration as conf +from cinder.volume.drivers.ibm import flashsystem_iscsi +from cinder.volume import volume_types + +LOG = logging.getLogger(__name__) + + +class FlashSystemManagementSimulator(fscommon.FlashSystemManagementSimulator): + def __init__(self): + # Default protocol is iSCSI + self._protocol = 'iSCSI' + self._volumes_list = {} + self._hosts_list = {} + self._mappings_list = {} + self._next_cmd_error = { + 'lsnode': '', + 'lssystem': '', + 'lsmdiskgrp': '' + } + self._errors = { + # CMMVC50000 is a fake error which indicates that command has not + # got expected results. This error represents kinds of CLI errors. + 'CMMVC50000': ('', 'CMMVC50000 The command can not be executed ' + 'successfully.') + } + + +class FlashSystemFakeISCSIDriver(flashsystem_iscsi.FlashSystemISCSIDriver): + def __init__(self, *args, **kwargs): + super(FlashSystemFakeISCSIDriver, self).__init__(*args, **kwargs) + + def set_fake_storage(self, fake): + self.fake_storage = fake + + def _ssh(self, cmd, check_exit_code=True): + ret = None + try: + LOG.debug('Run CLI command: %s', cmd) + utils.check_ssh_injection(cmd) + ret = self.fake_storage.execute_command(cmd, check_exit_code) + (stdout, stderr) = ret + LOG.debug('CLI output:\n stdout: %(stdout)s\n stderr: ' + '%(stderr)s', {'stdout': stdout, 'stderr': stderr}) + + except processutils.ProcessExecutionError as e: + with excutils.save_and_reraise_exception(): + LOG.debug('CLI Exception output:\n stdout: %(out)s\n ' + 'stderr: %(err)s', {'out': e.stdout, + 'err': e.stderr}) + return ret + + +class FlashSystemISCSIDriverTestCase(test.TestCase): + + def _set_flag(self, flag, value): + group = self.driver.configuration.config_group + self.driver.configuration.set_override(flag, value, group) + + def _reset_flags(self): + self.driver.configuration.local_conf.reset() + for k, v in self._def_flags.iteritems(): + self._set_flag(k, v) + + def _generate_vol_info(self, + vol_name, + vol_size=10, + vol_status='available'): + rand_id = six.text_type(random.randint(10000, 99999)) + if not vol_name: + vol_name = 'test_volume%s' % rand_id + + return {'name': vol_name, + 'size': vol_size, + 'id': '%s' % rand_id, + 'volume_type_id': None, + 'status': vol_status, + 'mdisk_grp_name': 'mdiskgrp0'} + + def _generate_snap_info(self, + vol_name, + vol_id, + vol_size, + vol_status, + snap_status='available'): + rand_id = six.text_type(random.randint(10000, 99999)) + return {'name': 'test_snap_%s' % rand_id, + 'id': rand_id, + 'volume': {'name': vol_name, + 'id': vol_id, + 'size': vol_size, + 'status': vol_status}, + 'volume_size': vol_size, + 'status': snap_status, + 'mdisk_grp_name': 'mdiskgrp0'} + + def setUp(self): + super(FlashSystemISCSIDriverTestCase, self).setUp() + + self._def_flags = {'san_ip': 'hostname', + 'san_login': 'username', + 'san_password': 'password', + 'flashsystem_connection_protocol': 'iSCSI', + 'flashsystem_multipath_enabled': False, + 'flashsystem_multihostmap_enabled': True, + 'iscsi_ip_address': '192.168.1.10', + 'flashsystem_iscsi_portid': 1} + + self.connector = { + 'host': 'flashsystem', + 'wwnns': ['0123456789abcdef', '0123456789abcdeg'], + 'wwpns': ['abcd000000000001', 'abcd000000000002'], + 'initiator': 'iqn.123456'} + + self.sim = FlashSystemManagementSimulator() + self.driver = FlashSystemFakeISCSIDriver( + configuration=conf.Configuration(None)) + self.driver.set_fake_storage(self.sim) + + self._reset_flags() + self.ctxt = context.get_admin_context() + self.driver.do_setup(None) + self.driver.check_for_setup_error() + + self.sleeppatch = mock.patch('eventlet.greenthread.sleep') + self.sleeppatch.start() + + def tearDown(self): + self.sleeppatch.stop() + super(FlashSystemISCSIDriverTestCase, self).tearDown() + + def test_flashsystem_do_setup(self): + # case 1: set as iSCSI + self.sim.set_protocol('iSCSI') + self._set_flag('flashsystem_connection_protocol', 'iSCSI') + self.driver.do_setup(None) + self.assertEqual('iSCSI', self.driver._protocol) + + # clear environment + self.sim.set_protocol('iSCSI') + self._reset_flags() + + def test_flashsystem_validate_connector(self): + conn_neither = {'host': 'host'} + conn_iscsi = {'host': 'host', 'initiator': 'foo'} + conn_both = {'host': 'host', 'initiator': 'foo', 'wwpns': 'bar'} + + protocol = self.driver._protocol + + # case 1: when protocol is iSCSI + self.driver._protocol = 'iSCSI' + self.driver.validate_connector(conn_iscsi) + self.driver.validate_connector(conn_both) + self.assertRaises(exception.InvalidConnectorException, + self.driver.validate_connector, conn_neither) + + # clear environment + self.driver._protocol = protocol + + def test_flashsystem_connection(self): + # case 1: initialize_connection/terminate_connection with iSCSI + self.sim.set_protocol('iSCSI') + self._set_flag('flashsystem_connection_protocol', 'iSCSI') + self.driver.do_setup(None) + vol1 = self._generate_vol_info(None) + self.driver.create_volume(vol1) + self.driver.initialize_connection(vol1, self.connector) + self.driver.terminate_connection(vol1, self.connector) + + # clear environment + self.driver.delete_volume(vol1) + self.sim.set_protocol('iSCSI') + self._reset_flags() + + def test_flashsystem_create_host(self): + # case 1: create host with iqn + self.sim.set_protocol('iSCSI') + self._set_flag('flashsystem_connection_protocol', 'iSCSI') + self.driver.do_setup(None) + conn = { + 'host': 'flashsystem', + 'wwnns': ['0123456789abcdef', '0123456789abcdeg'], + 'wwpns': ['abcd000000000001', 'abcd000000000002'], + 'initiator': 'iqn.123456'} + host = self.driver._create_host(conn) + + # case 2: delete host + self.driver._delete_host(host) + + # clear environment + self.sim.set_protocol('iSCSI') + self._reset_flags() + + def test_flashsystem_get_vdisk_params(self): + # case 1: use default params + self.driver._get_vdisk_params(None) + + # case 2: use extra params from type + opts1 = {'storage_protocol': 'iSCSI'} + opts2 = {'capabilities:storage_protocol': 'iSCSI'} + opts3 = {'storage_protocol': 'FC'} + type1 = volume_types.create(self.ctxt, 'opts1', opts1) + type2 = volume_types.create(self.ctxt, 'opts2', opts2) + type3 = volume_types.create(self.ctxt, 'opts3', opts3) + self.assertEqual( + 'iSCSI', + self.driver._get_vdisk_params(type1['id'])['protocol']) + self.assertEqual( + 'iSCSI', + self.driver._get_vdisk_params(type2['id'])['protocol']) + self.assertRaises(exception.InvalidInput, + self.driver._get_vdisk_params, + type3['id']) + + # clear environment + volume_types.destroy(self.ctxt, type1['id']) + volume_types.destroy(self.ctxt, type2['id']) + volume_types.destroy(self.ctxt, type3['id']) + + def test_flashsystem_map_vdisk_to_host(self): + # case 1: no host found + vol1 = self._generate_vol_info(None) + self.driver.create_volume(vol1) + self.assertEqual( + # lun id shoud begin with 1 + 1, + self.driver._map_vdisk_to_host(vol1['name'], self.connector)) + + # case 2: host already exists + vol2 = self._generate_vol_info(None) + self.driver.create_volume(vol2) + self.assertEqual( + # lun id shoud be sequential + 2, + self.driver._map_vdisk_to_host(vol2['name'], self.connector)) + + # case 3: test if already mapped + self.assertEqual( + 1, + self.driver._map_vdisk_to_host(vol1['name'], self.connector)) + + # clean environment + self.driver._unmap_vdisk_from_host(vol1['name'], self.connector) + self.driver._unmap_vdisk_from_host(vol2['name'], self.connector) + self.driver.delete_volume(vol1) + self.driver.delete_volume(vol2) + + # case 4: If there is no vdisk mapped to host, host should be removed + self.assertEqual( + None, + self.driver._get_host_from_connector(self.connector)) diff --git a/cinder/volume/drivers/ibm/flashsystem_iscsi.py b/cinder/volume/drivers/ibm/flashsystem_iscsi.py new file mode 100644 index 000000000..e7f3185ae --- /dev/null +++ b/cinder/volume/drivers/ibm/flashsystem_iscsi.py @@ -0,0 +1,450 @@ +# Copyright 2015 IBM Corp. +# 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. +# + + +""" +Volume driver for IBM FlashSystem storage systems with iSCSI protocol. + +Limitations: +1. Cinder driver only works when open_access_enabled=off. + +""" + +import random +import threading + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import excutils +import six + +from cinder import exception +from cinder.i18n import _, _LE, _LW +import cinder.volume.driver +from cinder.volume.drivers.ibm import flashsystem as fscommon +from cinder.volume.drivers.san import san + +LOG = logging.getLogger(__name__) + +flashsystem_iscsi_opts = [ + cfg.IntOpt('flashsystem_iscsi_portid', + default=0, + help='Default iSCSI Port ID of FlashSystem. ' + '(Default port is 0.)') +] + +CONF = cfg.CONF +CONF.register_opts(flashsystem_iscsi_opts) + + +class FlashSystemISCSIDriver(fscommon.FlashSystemDriver, + cinder.volume.driver.ISCSIDriver): + """IBM FlashSystem iSCSI volume driver + + Version history: + 1.0.2 - Initial driver for iSCSI + + """ + + VERSION = "1.0.2" + + def __init__(self, *args, **kwargs): + super(FlashSystemISCSIDriver, self).__init__(*args, **kwargs) + self.configuration.append_config_values(fscommon.flashsystem_opts) + self.configuration.append_config_values(flashsystem_iscsi_opts) + self.configuration.append_config_values(san.san_opts) + + def _check_vdisk_params(self, params): + # Check that the requested protocol is enabled + if not params['protocol'] in self._protocol: + msg = (_("'%(prot)s' is invalid for " + "flashsystem_connection_protocol " + "in config file. valid value(s) are " + "%(enabled)s.") + % {'prot': params['protocol'], + 'enabled': self._protocol}) + raise exception.InvalidInput(reason=msg) + + # Check if iscsi_ip is set when protocol is iSCSI + if params['protocol'] == 'iSCSI' and params['iscsi_ip'] == 'None': + msg = _("iscsi_ip_address must be set in config file when " + "using protocol 'iSCSI'.") + raise exception.InvalidInput(reason=msg) + + def _create_host(self, connector): + """Create a new host on the storage system. + + We create a host and associate it with the given connection + information. + """ + + LOG.debug('enter: _create_host: host %s.', connector['host']) + + rand_id = six.text_type(random.randint(0, 99999999)).zfill(8) + host_name = '%s-%s' % (self._connector_to_hostname_prefix(connector), + rand_id) + + ports = [] + + if 'iSCSI' == self._protocol and 'initiator' in connector: + ports.append('-iscsiname %s' % connector['initiator']) + + self._driver_assert(ports, + (_('_create_host: No connector ports.'))) + port1 = ports.pop(0) + arg_name, arg_val = port1.split() + ssh_cmd = ['svctask', 'mkhost', '-force', arg_name, arg_val, '-name', + '"%s"' % host_name] + out, err = self._ssh(ssh_cmd) + self._assert_ssh_return('successfully created' in out, + '_create_host', ssh_cmd, out, err) + + for port in ports: + arg_name, arg_val = port.split() + ssh_cmd = ['svctask', 'addhostport', '-force', + arg_name, arg_val, host_name] + out, err = self._ssh(ssh_cmd) + self._assert_ssh_return( + (not out.strip()), + '_create_host', ssh_cmd, out, err) + + LOG.debug( + 'leave: _create_host: host %(host)s - %(host_name)s.', + {'host': connector['host'], 'host_name': host_name}) + + return host_name + + def _find_host_exhaustive(self, connector, hosts): + for host in hosts: + ssh_cmd = ['svcinfo', 'lshost', '-delim', '!', host] + out, err = self._ssh(ssh_cmd) + self._assert_ssh_return( + out.strip(), + '_find_host_exhaustive', ssh_cmd, out, err) + for attr_line in out.split('\n'): + # If '!' not found, return the string and two empty strings + attr_name, foo, attr_val = attr_line.partition('!') + if (attr_name == 'iscsi_name' and + 'initiator' in connector and + attr_val == connector['initiator']): + return host + return None + + def _get_vdisk_map_properties( + self, connector, lun_id, vdisk_name, vdisk_id, vdisk_params): + """Get the map properties of vdisk.""" + + LOG.debug( + 'enter: _get_vdisk_map_properties: vdisk ' + '%(vdisk_name)s.', {'vdisk_name': vdisk_name}) + + preferred_node = '0' + IO_group = '0' + + # Get preferred node and other nodes in I/O group + preferred_node_entry = None + io_group_nodes = [] + for k, node in self._storage_nodes.items(): + if vdisk_params['protocol'] != node['protocol']: + continue + if node['id'] == preferred_node: + preferred_node_entry = node + if node['IO_group'] == IO_group: + io_group_nodes.append(node) + + if not io_group_nodes: + msg = (_('No node found in I/O group %(gid)s for volume %(vol)s.') + % {'gid': IO_group, 'vol': vdisk_name}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + if not preferred_node_entry and not vdisk_params['multipath']: + # Get 1st node in I/O group + preferred_node_entry = io_group_nodes[0] + LOG.warning(_LW('_get_vdisk_map_properties: Did not find a ' + 'preferred node for vdisk %s.'), vdisk_name) + properties = { + 'target_discovered': False, + 'target_lun': lun_id, + 'volume_id': vdisk_id, + } + + type_str = 'iscsi' + if preferred_node_entry['ipv4']: + ipaddr = preferred_node_entry['ipv4'][0] + else: + ipaddr = preferred_node_entry['ipv6'][0] + iscsi_port = self.configuration.iscsi_port + properties['target_portal'] = '%s:%s' % (ipaddr, iscsi_port) + properties['target_iqn'] = preferred_node_entry['iscsi_name'] + + LOG.debug( + 'leave: _get_vdisk_map_properties: vdisk ' + '%(vdisk_name)s.', {'vdisk_name': vdisk_name}) + + return {'driver_volume_type': type_str, 'data': properties} + + def initialize_connection(self, volume, connector): + """Perform work so that an iSCSI connection can be made. + + To be able to create an iSCSI connection from a given host to a + volume, we must: + 1. Translate the given iSCSI name to a host name + 2. Create new host on the storage system if it does not yet exist + 3. Map the volume to the host if it is not already done + 4. Return the connection information for relevant nodes (in the + proper I/O group) + + """ + + LOG.debug( + 'enter: initialize_connection: volume %(vol)s with ' + 'connector %(conn)s.', {'vol': volume, 'conn': connector}) + + vdisk_name = volume['name'] + vdisk_id = volume['id'] + vdisk_params = self._get_vdisk_params(volume['volume_type_id']) + + self._wait_vdisk_copy_completed(vdisk_name) + + self._driver_assert( + self._is_vdisk_defined(vdisk_name), + (_('initialize_connection: vdisk %s is not defined.') + % vdisk_name)) + + lun_id = self._map_vdisk_to_host(vdisk_name, connector) + + properties = {} + try: + properties = self._get_vdisk_map_properties( + connector, lun_id, vdisk_name, vdisk_id, vdisk_params) + except exception.VolumeBackendAPIException: + with excutils.save_and_reraise_exception(): + self.terminate_connection(volume, connector) + LOG.error(_LE('Failed to collect return properties for ' + 'volume %(vol)s and connector %(conn)s.'), + {'vol': volume, 'conn': connector}) + + LOG.debug( + 'leave: initialize_connection:\n volume: %(vol)s\n connector ' + '%(conn)s\n properties: %(prop)s.', + {'vol': volume, + 'conn': connector, + 'prop': properties}) + + return properties + + def terminate_connection(self, volume, connector, **kwargs): + """Cleanup after connection has been terminated. + + When we clean up a terminated connection between a given connector + and volume, we: + 1. Translate the given connector to a host name + 2. Remove the volume-to-host mapping if it exists + 3. Delete the host if it has no more mappings (hosts are created + automatically by this driver when mappings are created) + """ + LOG.debug( + 'enter: terminate_connection: volume %(vol)s with ' + 'connector %(conn)s.', + {'vol': volume, 'conn': connector}) + + vdisk_name = volume['name'] + self._wait_vdisk_copy_completed(vdisk_name) + self._unmap_vdisk_from_host(vdisk_name, connector) + + LOG.debug( + 'leave: terminate_connection: volume %(vol)s with ' + 'connector %(conn)s.', {'vol': volume, 'conn': connector}) + + return {'driver_volume_type': 'iscsi'} + + def _get_iscsi_ip_addrs(self): + """get ip address of iSCSI interface.""" + + LOG.debug('enter: _get_iscsi_ip_addrs') + + cmd = ['svcinfo', 'lsportip'] + generator = self._port_conf_generator(cmd) + header = next(generator, None) + if not header: + return + + for key in self._storage_nodes: + if self._storage_nodes[key]['config_node'] == 'yes': + node = self._storage_nodes[key] + break + + if node is None: + msg = _('No config node found.') + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + for port_data in generator: + try: + port_ipv4 = port_data['IP_address'] + port_ipv6 = port_data['IP_address_6'] + state = port_data['state'] + speed = port_data['speed'] + except KeyError: + self._handle_keyerror('lsportip', header) + if port_ipv4 == self.configuration.iscsi_ip_address and ( + port_data['id'] == ( + six.text_type( + self.configuration.flashsystem_iscsi_portid))): + if state not in ('configured', 'online'): + msg = (_('State of node is wrong. Current state is %s.') + % state) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + if state in ('configured', 'online') and speed != 'NONE': + if port_ipv4: + node['ipv4'].append(port_ipv4) + if port_ipv6: + node['ipv6'].append(port_ipv6) + break + if not (len(node['ipv4']) or len(node['ipv6'])): + msg = _('No ip address found.') + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + LOG.debug('leave: _get_iscsi_ip_addrs') + + def do_setup(self, ctxt): + """Check that we have all configuration details from the storage.""" + + LOG.debug('enter: do_setup') + + self._context = ctxt + + # Get data of configured node + self._get_node_data() + + # Get the iSCSI IP addresses of the FlashSystem nodes + self._get_iscsi_ip_addrs() + + for k, node in self._storage_nodes.items(): + if self.configuration.flashsystem_connection_protocol == 'iSCSI': + if (len(node['ipv4']) or len(node['ipv6']) and + len(node['iscsi_name'])): + node['protocol'] = 'iSCSI' + + self._protocol = 'iSCSI' + + # Set for vdisk synchronization + self._vdisk_copy_in_progress = set() + self._vdisk_copy_lock = threading.Lock() + self._check_lock_interval = 5 + + LOG.debug('leave: do_setup') + + def _get_node_data(self): + """Get and verify node configuration.""" + + LOG.debug('enter: _get_node_data') + + # Get storage system name and id + ssh_cmd = ['svcinfo', 'lssystem', '-delim', '!'] + attributes = self._execute_command_and_parse_attributes(ssh_cmd) + if not attributes or not ('name' in attributes): + msg = _('Could not get system name.') + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + self._system_name = attributes['name'] + self._system_id = attributes['id'] + + # Validate value of open_access_enabled flag, for now only + # support when open_access_enabled is off + if not attributes or not ('open_access_enabled' in attributes) or ( + attributes['open_access_enabled'] != 'off'): + msg = _('open_access_enabled is not off.') + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + # Validate that the array exists + pool = fscommon.FLASHSYSTEM_VOLPOOL_NAME + ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!', pool] + attributes = self._execute_command_and_parse_attributes(ssh_cmd) + if not attributes: + LOG.debug('_get_node_data: lssystem attributes:', attributes) + msg = _('Unable to parse attributes') + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + if not ('status' in attributes) or ( + attributes['status'] == 'offline'): + msg = (_('Array does not exist or is offline. ' + 'Current status of array is %ds.') + % attributes['status']) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + # Get the iSCSI names of the FlashSystem nodes + ssh_cmd = ['svcinfo', 'lsnode', '-delim', '!'] + out, err = self._ssh(ssh_cmd) + self._assert_ssh_return( + out.strip(), '_get_config_data', ssh_cmd, out, err) + + nodes = out.strip().splitlines() + self._assert_ssh_return(nodes, '_get_node_data', ssh_cmd, out, err) + header = nodes.pop(0) + for node_line in nodes: + try: + node_data = self._get_hdr_dic(header, node_line, '!') + except exception.VolumeBackendAPIException: + with excutils.save_and_reraise_exception(): + self._log_cli_output_error('_get_node_data', + ssh_cmd, out, err) + try: + node = { + 'id': node_data['id'], + 'name': node_data['name'], + 'IO_group': node_data['IO_group_id'], + 'WWNN': node_data['WWNN'], + 'status': node_data['status'], + 'WWPN': [], + 'protocol': None, + 'iscsi_name': node_data['iscsi_name'], + 'config_node': node_data['config_node'], + 'ipv4': [], + 'ipv6': [], + } + if node['status'] == 'online': + self._storage_nodes[node['id']] = node + except KeyError: + self._handle_keyerror('lsnode', header) + + LOG.debug('leave: _get_iscsi_ip_addrs') + + def _build_default_params(self): + protocol = self.configuration.flashsystem_connection_protocol + if protocol.lower() == 'iscsi': + protocol = 'iSCSI' + return {'protocol': protocol, + 'multipath': self.configuration.flashsystem_multipath_enabled, + 'iscsi_ip': self.configuration.iscsi_ip_address, + 'iscsi_port': self.configuration.iscsi_port, + 'iscsi_ported': self.configuration.flashsystem_iscsi_portid} + + def validate_connector(self, connector): + """Check connector for enabled protocol.""" + valid = False + if 'iSCSI' == self._protocol and 'initiator' in connector: + valid = True + if not valid: + msg = _LE('The connector does not contain the ' + 'required information: initiator is missing') + LOG.error(msg) + raise exception.InvalidConnectorException(missing=( + 'initiator'))