]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add iSCSI protocol support for IBM FlashSystem
authorEdwin Wang <edwin.wang@cn.ibm.com>
Sat, 13 Dec 2014 20:42:09 +0000 (04:42 +0800)
committerEdwin Wang <edwin.wang@cn.ibm.com>
Tue, 9 Jun 2015 06:21:46 +0000 (14:21 +0800)
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

cinder/tests/unit/test_ibm_flashsystem_iscsi.py [new file with mode: 0644]
cinder/volume/drivers/ibm/flashsystem_iscsi.py [new file with mode: 0644]

diff --git a/cinder/tests/unit/test_ibm_flashsystem_iscsi.py b/cinder/tests/unit/test_ibm_flashsystem_iscsi.py
new file mode 100644 (file)
index 0000000..2971b17
--- /dev/null
@@ -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 (file)
index 0000000..e7f3185
--- /dev/null
@@ -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'))