[SVf] Manage host attachment using portsets

[Spectrum Virtualize family] Providing support to IBM Storwize cinder
driver to use portsets to manage host attachment, backend storage
connectivity and IP replication traffic.
Portsets are groupings of logical addresses that are associated with
specific traffic types. Portsets are created on the storage. Hosts are
then created using the portset info given by the admin.

Implements: blueprint ibm-svf-portset

Change-Id: I38c9889bff2d18d3dadd49f28b8436a77b131a22
This commit is contained in:
haailani 2022-02-21 14:46:46 +00:00 committed by Harsh Ailani
parent ac0779ad18
commit 88fd602edd
5 changed files with 279 additions and 67 deletions

View File

@ -88,7 +88,9 @@ class StorwizeSVCManagementSimulator(object):
self._other_pools = {'openstack2': {}, 'openstack3': {}}
self._next_cmd_error = {
'lsportip': '',
'lsip': '',
'lsfabric': '',
'lsfcportsetmember': '',
'lsiscsiauth': '',
'lsnodecanister': '',
'mkvdisk': '',
@ -747,45 +749,45 @@ port_speed!N/A
ports = [None] * 17
ports[0] = ['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id',
'current_node_id', 'nportid', 'host_io_permitted',
'virtualized']
'virtualized', 'fc_io_port_id']
ports[1] = ['0', '5005076801106CFE', '5005076801106CFE', '1', '1',
'1', '042200', 'no', 'no']
'1', '042200', 'no', 'no', '']
ports[2] = ['0', '5005076801996CFE', '5005076801106CFE', '1', '1',
'1', '042200', 'yes', 'yes']
'1', '042200', 'yes', 'yes', '']
ports[3] = ['0', '5005076801206CFE', '5005076801106CFE', '2', '1',
'1', '042200', 'no', 'no']
'1', '042200', 'no', 'no', '']
ports[4] = ['0', '5005076801A96CFE', '5005076801106CFE', '2', '1',
'1', '042200', 'yes', 'yes']
'1', '042200', 'yes', 'yes', '']
ports[5] = ['0', '5005076801306CFE', '5005076801106CFE', '3', '1',
'', '042200', 'no', 'no']
'', '042200', 'no', 'no', '']
ports[6] = ['0', '5005076801B96CFE', '5005076801106CFE', '3', '1',
'', '042200', 'yes', 'yes']
'', '042200', 'yes', 'yes', '']
ports[7] = ['0', '5005076801406CFE', '5005076801106CFE', '4', '1',
'', '042200', 'no', 'no']
'', '042200', 'no', 'no', '']
ports[8] = ['0', '5005076801C96CFE', '5005076801106CFE', '4', '1',
'', '042200', 'yes', 'yes']
'', '042200', 'yes', 'yes', '']
ports[9] = ['0', '5005076801101806', '5005076801101806', '1', '2',
'2', '042200', 'no', 'no']
'2', '042200', 'no', 'no', '']
ports[10] = ['0', '5005076801991806', '5005076801101806', '1', '2',
'2', '042200', 'yes', 'yes']
'2', '042200', 'yes', 'yes', '']
ports[11] = ['0', '5005076801201806', '5005076801101806', '2', '2',
'2', '042200', 'no', 'no']
'2', '042200', 'no', 'no', '']
ports[12] = ['0', '5005076801A91806', '5005076801101806', '2', '2',
'2', '042200', 'yes', 'yes']
'2', '042200', 'yes', 'yes', '']
ports[13] = ['0', '5005076801301806', '5005076801101806', '3', '2',
'', '042200', 'no', 'no']
'', '042200', 'no', 'no', '']
ports[14] = ['0', '5005076801B91806', '5005076801101806', '3', '2',
'', '042200', 'yes', 'yes']
'', '042200', 'yes', 'yes', '']
ports[15] = ['0', '5005076801401806', '5005076801101806', '4', '2',
'', '042200', 'no', 'no']
'', '042200', 'no', 'no', '']
ports[16] = ['0', '5005076801C91806', '5005076801101806', '4', '2',
'', '042200', 'yes', 'yes']
'', '042200', 'yes', 'yes', '']
if 'filtervalue' in kwargs:
rows = []
rows.append(['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id',
'current_node_id', 'nportid', 'host_io_permitted',
'virtualized'])
'virtualized', 'fc_io_port_id'])
if ':' in kwargs['filtervalue']:
filter1 = kwargs['filtervalue'].split(':')[0]
@ -805,6 +807,73 @@ port_speed!N/A
rows = ports
return self._print_info_cmd(rows=rows, **kwargs)
# Print mostly made-up stuff in the correct syntax
def _cmd_lsfcportsetmember(self, **kwargs):
rows = [None] * 7
rows[0] = ['id', 'fc_io_port_id', 'portset_id', 'portset_name',
'owner_id', 'owner_name']
rows[1] = ['0', '5', '6', 'portset6', '', '']
rows[2] = ['1', '5', '64', 'portset64', '', '']
rows[3] = ['2', '6', '6', 'portset6', '', '']
rows[4] = ['3', '6', '64', 'portset64', '', '']
rows[5] = ['4', '7', '64', 'portset64', '', '']
rows[6] = ['5', '8', '64', 'portset64', '', '']
if self._next_cmd_error['lsfcportsetmember'] == 'header_mismatch':
rows[0].pop(2)
self._next_cmd_error['lsfcportsetmember'] = ''
if self._next_cmd_error['lsfcportsetmember'] == 'remove_field':
for row in rows:
row.pop(1)
self._next_cmd_error['lsfcportsetmember'] = ''
return self._print_info_cmd(rows=rows, **kwargs)
# Print mostly made-up stuff in the correct syntax
def _cmd_lsip(self, **kwargs):
ports = [None] * 9
ports[0] = ['id', 'node_id', 'node_name', 'port_id', 'portset_id',
'portset_name', 'IP_address', 'prefix', 'vlan', 'gateway',
'owner_id', 'owner_name']
ports[1] = ['0', '1', 'node1', '5', '0', 'portset0', '1.234.50.11',
'24', '1001', '', '', '']
ports[2] = ['1', '1', 'node1', '6', '4', 'portset4', '1.234.51.11',
'24', '1002', '', '', '']
ports[3] = ['2', '1', 'node1', '7', '5', 'portset5', '1.234.52.11',
'24', '1003', '', '', '']
ports[4] = ['3', '1', 'node1', '8', '6', 'portset6', '1.234.53.11',
'24', '1004', '', '', '']
ports[5] = ['4', '2', 'node2', '5', '0', 'portset0', '1.234.54.11',
'24', '1005', '', '', '']
ports[6] = ['5', '2', 'node2', '6', '4', 'portset4', '1.234.55.11',
'24', '1006', '', '', '']
ports[7] = ['6', '2', 'node2', '7', '5', 'portset5', '1.234.56.11',
'24', '1007', '', '', '']
ports[8] = ['7', '2', 'node2', '8', '6', 'portset6', '1.234.57.11',
'24', '1008', '', '', '']
if 'filtervalue' in kwargs:
rows = []
rows.append(['id', 'node_id', 'node_name', 'port_id', 'portset_id',
'portset_name', 'IP_address', 'prefix', 'vlan',
'gateway', 'owner_id', 'owner_name'])
value = kwargs['filtervalue'].split('=')[1]
for v in ports:
if six.text_type(v[5]) == value:
rows.append(v)
else:
rows = ports
if self._next_cmd_error['lsip'] == 'header_mismatch':
rows[0].pop(2)
self._next_cmd_error['lsip'] = ''
if self._next_cmd_error['lsip'] == 'remove_field':
for row in rows:
row.pop(1)
self._next_cmd_error['lsip'] = ''
return self._print_info_cmd(rows=rows, **kwargs)
# Print mostly made-up stuff in the correct syntax
def _cmd_lsportip(self, **kwargs):
if self._next_cmd_error['lsportip'] == 'ip_no_config':
@ -1247,6 +1316,10 @@ port_speed!N/A
host_info['site_name'] = kwargs['site'].strip('\'\"')
else:
host_info['site_name'] = ''
if 'portset' in kwargs:
host_info['portset_name'] = kwargs['portset'].strip('\'\"')
else:
host_info['portset_name'] = ''
out, err = self._add_port_to_host(host_info, **kwargs)
if not len(err):
self._hosts_list[host_name] = host_info
@ -4826,6 +4899,26 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.sim.error_injection('lsportip', 'remove_field')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.do_setup, None)
self.sim.error_injection('lsfcportsetmember', 'invalid_input')
self.driver.do_setup(None)
with mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_system_info') as get_system_info:
fake_system_info = {'code_level': (8, 5, 0, 0),
'topology': 'standard',
'system_name': 'storwize-svc-sim',
'system_id': '0123456789ABCDEF'}
get_system_info.return_value = fake_system_info
if self.USESIM:
self.sim.error_injection('lsip', 'invalid_portset')
self.driver.do_setup(None)
self.sim.error_injection('lsip', 'header_mismatch')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.do_setup, None)
self.sim.error_injection('lsip', 'remove_field')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.do_setup, None)
# Check with bad parameters
self._set_flag('san_ip', '')
@ -4873,6 +4966,37 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Finally, check with good parameters
self.driver.do_setup(None)
@mock.patch.object(storwize_svc_common.StorwizeSSH,
'mkhost')
def test_storwize_create_host_with_portset(self, mkhost):
self.driver.do_setup(self.ctxt)
connector = {'host': 'storwize-svc-host',
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa',
'ip': '127.0.0.1'}
# Using portset other than default portset0
portset = "portset1"
self.driver._helpers.create_host(connector, iscsi=True,
portset=portset)
host_name = self.driver._helpers.get_host_from_connector(
connector, iscsi=True)
self.assertIsNotNone(host_name)
@mock.patch.object(storwize_svc_common.StorwizeSSH,
'mkhost')
def test_storwize_create_host_with_portset_from_config(self, mkhost):
self.driver.do_setup(self.ctxt)
connector = {'host': 'storwize-svc-host',
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa',
'ip': '127.0.0.1'}
# Using portset other than default portset0
self._set_flag('storwize_portset', "portset1")
self.driver._helpers.create_host(
connector, iscsi=True,
portset=self.driver.configuration.storwize_portset)
host_name = self.driver._helpers.get_host_from_connector(
connector, iscsi=True)
self.assertIsNotNone(host_name)
@mock.patch.object(ssh_utils, 'SSHPool')
@mock.patch.object(processutils, 'ssh_execute')
def test_run_ssh_set_up_with_san_ip(self, mock_ssh_execute, mock_ssh_pool):
@ -5288,6 +5412,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
'mirror_pool': None,
'volume_topology': None,
'peer_pool': None,
'storwize_portset': None,
'storwize_svc_src_child_pool': None,
'storwize_svc_target_child_pool': None,
'cycle_period_seconds': 300

View File

@ -133,6 +133,10 @@ storwize_svc_opts = [
default=None,
help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'),
cfg.StrOpt('storwize_portset',
default=None,
help='Specifies the name of the portset in which '
'host to be created.'),
cfg.StrOpt('storwize_svc_src_child_pool',
default=None,
help='Specifies the name of the source child pool in which '
@ -272,11 +276,13 @@ class StorwizeSSH(object):
port.append(port_name)
return port
def mkhost(self, host_name, port_type, port_name, site=None):
def mkhost(self, host_name, port_type, port_name, site=None, portset=None):
port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'mkhost', '-force'] + port
if site:
ssh_cmd += ['-site', '"%s"' % site]
if portset:
ssh_cmd += ['-portset', '"%s"' % portset]
ssh_cmd += ['-name', '"%s"' % host_name]
return self.run_ssh_check_created(ssh_cmd)
@ -318,6 +324,12 @@ class StorwizeSSH(object):
ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsip(self, portset=None):
ssh_cmd = ['svcinfo', 'lsip', '-delim', '!']
if portset:
ssh_cmd += ['-filtervalue', 'portset_name=%s' % portset]
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsfabric(self, wwpn=None, host=None):
ssh_cmd = ['svcinfo', 'lsfabric', '-delim', '!']
if wwpn:
@ -765,6 +777,10 @@ class StorwizeSSH(object):
ssh_cmd += ['-filtervalue', 'current_node_id=%s' % current_node_id]
return self.run_ssh_info(ssh_cmd, with_header=True)
def lsfcportsetmember(self):
ssh_cmd = ['svcinfo', 'lsfcportsetmember', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool, '-copy',
copy_id, '-vdisk', vdisk]
@ -1070,6 +1086,7 @@ class StorwizeHelpers(object):
node['WWPN'] = []
node['ipv4'] = []
node['ipv6'] = []
node['IP_address'] = []
node['enabled_protocols'] = []
nodes[node['id']] = node
node['site_id'] = (node_data['site_id']
@ -1080,10 +1097,26 @@ class StorwizeHelpers(object):
self.handle_keyerror('lsnode', node_data)
return nodes
def add_iscsi_ip_addrs(self, storage_nodes):
def add_iscsi_ip_addrs(self, storage_nodes, code_level, portset=None):
"""Add iSCSI IP addresses to system node information."""
resp = self.ssh.lsportip()
for ip_data in resp:
if code_level >= (8, 4, 2, 0):
portset_name = portset if portset else 'portset0'
lsip_resp = self.ssh.lsip(portset=portset_name)
for node_data in storage_nodes:
ip_addresses = []
try:
for ip_data in lsip_resp:
if ip_data['node_id'] in node_data:
if (ip_data['IP_address']):
ip_addresses.append(ip_data['IP_address'])
except KeyError:
self.handle_keyerror('lsip', ip_data)
if ip_addresses:
storage_nodes[ip_data['node_id']]['IP_address'] = (
ip_addresses)
else:
lsportip_resp = self.ssh.lsportip()
for ip_data in lsportip_resp:
try:
state = ip_data['state']
if ip_data['node_id'] in storage_nodes and (
@ -1110,19 +1143,35 @@ class StorwizeHelpers(object):
port_info['status'] == 'active'):
wwpns.add(port_info['WWPN'])
else:
npiv_wwpns = self.get_npiv_wwpns(node_id=node['id'])
npiv_wwpns = self.get_npiv_wwpns(code_level,
node_id=node['id'])
wwpns.update(npiv_wwpns)
node['WWPN'] = list(wwpns)
LOG.info('WWPN on node %(node)s: %(wwpn)s.',
{'node': node['id'], 'wwpn': node['WWPN']})
def get_npiv_wwpns(self, node_id=None, host_io=None):
def get_npiv_wwpns(self, code_level, node_id=None, host_io=None,
portset=None):
wwpns = set()
# In the response of lstargetportfc, the host_io_permitted
# indicates whether the port can be used for host I/O
resp = self.ssh.lstargetportfc(current_node_id=node_id,
targetportfc_resp = self.ssh.lstargetportfc(current_node_id=node_id,
host_io_permitted=host_io)
for port_info in resp:
if code_level >= (8, 4, 2, 0):
portset_name = portset if portset else 'portset64'
port_ids = set()
fcportsetmember_resp = self.ssh.lsfcportsetmember()
for portset_member in fcportsetmember_resp:
if portset_member['portset_name'] == portset_name:
port_ids.add(portset_member['fc_io_port_id'])
for port_info in targetportfc_resp:
for port_id in port_ids:
if port_id == port_info['fc_io_port_id']:
wwpns.add(port_info['WWPN'])
break
else:
for port_info in targetportfc_resp:
wwpns.add(port_info['WWPN'])
return list(wwpns)
@ -1288,7 +1337,7 @@ class StorwizeHelpers(object):
LOG.debug('Leave: get_host_from_connector: host %s.', host_name)
return host_name
def create_host(self, connector, iscsi=False, site=None):
def create_host(self, connector, iscsi=False, site=None, portset=None):
"""Create a new host on the storage system.
We create a host name and associate it with the given connection
@ -1343,7 +1392,7 @@ class StorwizeHelpers(object):
# Create a host with one port
port = ports.pop(0)
# Host site_id is necessary for hyperswap volume.
self.ssh.mkhost(host_name, port[0], port[1], site)
self.ssh.mkhost(host_name, port[0], port[1], site, portset)
# Add any additional ports to the host
for port in ports:
@ -1514,6 +1563,7 @@ class StorwizeHelpers(object):
'mirror_pool': config.storwize_svc_mirror_pool,
'volume_topology': None,
'peer_pool': config.storwize_peer_pool,
'storwize_portset': config.storwize_portset,
'storwize_svc_src_child_pool':
config.storwize_svc_src_child_pool,
'storwize_svc_target_child_pool':
@ -3279,14 +3329,15 @@ class StorwizeSVCCommonDriver(san.SanDriver,
state['storage_nodes'] = helper.get_node_info()
# Add the iSCSI IP addresses and WWPNs to the storage node info
helper.add_iscsi_ip_addrs(state['storage_nodes'])
helper.add_iscsi_ip_addrs(state['storage_nodes'], state['code_level'])
helper.add_fc_wwpns(state['storage_nodes'], state['code_level'])
# For each node, check what connection modes it supports. Delete any
# nodes that do not support any types (may be partially configured).
to_delete = []
for k, node in state['storage_nodes'].items():
if ((len(node['ipv4']) or len(node['ipv6']))
if ((len(node['ipv4']) or len(node['ipv6']) or
len(node['IP_address']))
and len(node['iscsi_name'])):
node['enabled_protocols'].append('iSCSI')
state['enabled_protocols'].add('iSCSI')

View File

@ -96,9 +96,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
2.2.3 - Add replication group support
2.2.4 - Add backup snapshots support
2.2.5 - Add hyperswap support
2.2.6 - Add support for host attachment using portsets
"""
VERSION = "2.2.5"
VERSION = "2.2.6"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -190,7 +191,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
# this connector info.
host_name = None
try:
host_name = backend_helper.create_host(connector, site=host_site)
opts = self._get_vdisk_params(volume.volume_type_id)
host_name = (
backend_helper.create_host(connector, site=host_site,
portset=opts['storwize_portset']))
except exception.VolumeBackendAPIException as excp:
if "CMMVC6035E" in excp.msg:
msg = (_('Host already exists for connector '
@ -266,8 +270,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
conn_wwpns.extend(node['WWPN'])
else:
npiv_wwpns = backend_helper.get_npiv_wwpns(
node_state['code_level'],
node_id=node['id'],
host_io="yes")
host_io="yes",
portset=opts['storwize_portset'])
conn_wwpns.extend(npiv_wwpns)
properties['target_wwn'] = conn_wwpns
@ -423,8 +429,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
if node_state['code_level'] < (7, 7, 0, 0):
conn_wwpns.extend(node['WWPN'])
else:
npivwwpns = backend_helper.get_npiv_wwpns(node_id=node['id'],
host_io="yes")
npivwwpns = (
backend_helper.get_npiv_wwpns(node_state['code_level'],
node_id=node['id'],
host_io="yes"))
conn_wwpns.extend(npivwwpns)
i_t_map = self._make_initiator_target_map(connector['wwpns'],

View File

@ -94,9 +94,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
2.2.2 - Add replication group support
2.2.3 - Add backup snapshots support
2.2.4 - Add hyperswap support
2.2.5 - Add support for host attachment using portsets
"""
VERSION = "2.2.4"
VERSION = "2.2.5"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -183,8 +184,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# this connector info.
host_name = None
try:
host_name = backend_helper.create_host(connector, iscsi=True,
site=host_site)
opts = self._get_vdisk_params(volume.volume_type_id)
host_name = (
backend_helper.create_host(connector, iscsi=True,
site=host_site,
portset=opts['storwize_portset']))
except exception.VolumeBackendAPIException as excp:
if "CMMVC6578E" in excp.msg:
msg = (_('Host already exists for connector '
@ -213,13 +217,15 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
try:
properties = self._get_single_iscsi_data(volume, connector,
lun_id, chap_secret)
lun_id, chap_secret,
opts['storwize_portset'])
multipath = connector.get('multipath', False)
if multipath:
properties = self._get_multi_iscsi_data(volume, connector,
properties = (
self._get_multi_iscsi_data(volume, connector,
lun_id, properties,
backend_helper,
node_state)
backend_helper, node_state,
opts['storwize_portset']))
except Exception as ex:
with excutils.save_and_reraise_exception():
LOG.error('initialize_connection: Failed to export volume '
@ -240,7 +246,8 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
return {'driver_volume_type': 'iscsi', 'data': properties, }
def _get_single_iscsi_data(self, volume, connector, lun_id, chap_secret):
def _get_single_iscsi_data(self, volume, connector, lun_id,
chap_secret, portset):
LOG.debug('enter: _get_single_iscsi_data: volume %(vol)s with '
'connector %(conn)s lun_id %(lun_id)s',
{'vol': volume.id, 'conn': connector,
@ -277,6 +284,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# Get preferred node and other nodes in I/O group
preferred_node_entry = None
io_group_nodes = []
if node_state['code_level'] >= (8, 4, 2, 0):
backend_helper.add_iscsi_ip_addrs(node_state['storage_nodes'],
node_state['code_level'],
portset=portset)
for node in node_state['storage_nodes'].values():
if self.protocol not in node['enabled_protocols']:
continue
@ -305,6 +317,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
'target_lun': lun_id,
'volume_id': volume.id}
if node_state['code_level'] >= (8, 4, 2, 0):
if preferred_node_entry['IP_address']:
ipaddr = preferred_node_entry['IP_address'][0]
else:
if preferred_node_entry['ipv4']:
ipaddr = preferred_node_entry['ipv4'][0]
else:
@ -327,13 +343,17 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
return properties
def _get_multi_iscsi_data(self, volume, connector, lun_id, properties,
backend_helper, node_state):
backend_helper, node_state, portset):
LOG.debug('enter: _get_multi_iscsi_data: volume %(vol)s with '
'connector %(conn)s lun_id %(lun_id)s',
{'vol': volume.id, 'conn': connector,
'lun_id': lun_id})
try:
if node_state['code_level'] >= (8, 4, 2, 0):
portset_name = portset if portset else 'portset0'
resp = backend_helper.ssh.lsip(portset=portset_name)
else:
resp = backend_helper.ssh.lsportip()
except Exception as ex:
msg = (_('_get_multi_iscsi_data: Failed to '
@ -351,6 +371,9 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
continue
link_state = ip_data.get('link_state', None)
valid_port = ''
if node_state['code_level'] >= (8, 4, 2, 0):
valid_port = ip_data['IP_address']
else:
if ((ip_data['state'] == 'configured' and
link_state == 'active') or
ip_data['state'] == 'online'):

View File

@ -0,0 +1,5 @@
---
features:
- |
IBM Spectrum Virtualize Family driver: Added support to manage host
attachment using portsets for code level >= 8.4.2.0