--- /dev/null
+# 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))
--- /dev/null
+# 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'))