[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._other_pools = {'openstack2': {}, 'openstack3': {}}
self._next_cmd_error = { self._next_cmd_error = {
'lsportip': '', 'lsportip': '',
'lsip': '',
'lsfabric': '', 'lsfabric': '',
'lsfcportsetmember': '',
'lsiscsiauth': '', 'lsiscsiauth': '',
'lsnodecanister': '', 'lsnodecanister': '',
'mkvdisk': '', 'mkvdisk': '',
@ -747,45 +749,45 @@ port_speed!N/A
ports = [None] * 17 ports = [None] * 17
ports[0] = ['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id', ports[0] = ['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id',
'current_node_id', 'nportid', 'host_io_permitted', 'current_node_id', 'nportid', 'host_io_permitted',
'virtualized'] 'virtualized', 'fc_io_port_id']
ports[1] = ['0', '5005076801106CFE', '5005076801106CFE', '1', '1', ports[1] = ['0', '5005076801106CFE', '5005076801106CFE', '1', '1',
'1', '042200', 'no', 'no'] '1', '042200', 'no', 'no', '']
ports[2] = ['0', '5005076801996CFE', '5005076801106CFE', '1', '1', ports[2] = ['0', '5005076801996CFE', '5005076801106CFE', '1', '1',
'1', '042200', 'yes', 'yes'] '1', '042200', 'yes', 'yes', '']
ports[3] = ['0', '5005076801206CFE', '5005076801106CFE', '2', '1', ports[3] = ['0', '5005076801206CFE', '5005076801106CFE', '2', '1',
'1', '042200', 'no', 'no'] '1', '042200', 'no', 'no', '']
ports[4] = ['0', '5005076801A96CFE', '5005076801106CFE', '2', '1', ports[4] = ['0', '5005076801A96CFE', '5005076801106CFE', '2', '1',
'1', '042200', 'yes', 'yes'] '1', '042200', 'yes', 'yes', '']
ports[5] = ['0', '5005076801306CFE', '5005076801106CFE', '3', '1', ports[5] = ['0', '5005076801306CFE', '5005076801106CFE', '3', '1',
'', '042200', 'no', 'no'] '', '042200', 'no', 'no', '']
ports[6] = ['0', '5005076801B96CFE', '5005076801106CFE', '3', '1', ports[6] = ['0', '5005076801B96CFE', '5005076801106CFE', '3', '1',
'', '042200', 'yes', 'yes'] '', '042200', 'yes', 'yes', '']
ports[7] = ['0', '5005076801406CFE', '5005076801106CFE', '4', '1', ports[7] = ['0', '5005076801406CFE', '5005076801106CFE', '4', '1',
'', '042200', 'no', 'no'] '', '042200', 'no', 'no', '']
ports[8] = ['0', '5005076801C96CFE', '5005076801106CFE', '4', '1', ports[8] = ['0', '5005076801C96CFE', '5005076801106CFE', '4', '1',
'', '042200', 'yes', 'yes'] '', '042200', 'yes', 'yes', '']
ports[9] = ['0', '5005076801101806', '5005076801101806', '1', '2', ports[9] = ['0', '5005076801101806', '5005076801101806', '1', '2',
'2', '042200', 'no', 'no'] '2', '042200', 'no', 'no', '']
ports[10] = ['0', '5005076801991806', '5005076801101806', '1', '2', ports[10] = ['0', '5005076801991806', '5005076801101806', '1', '2',
'2', '042200', 'yes', 'yes'] '2', '042200', 'yes', 'yes', '']
ports[11] = ['0', '5005076801201806', '5005076801101806', '2', '2', ports[11] = ['0', '5005076801201806', '5005076801101806', '2', '2',
'2', '042200', 'no', 'no'] '2', '042200', 'no', 'no', '']
ports[12] = ['0', '5005076801A91806', '5005076801101806', '2', '2', ports[12] = ['0', '5005076801A91806', '5005076801101806', '2', '2',
'2', '042200', 'yes', 'yes'] '2', '042200', 'yes', 'yes', '']
ports[13] = ['0', '5005076801301806', '5005076801101806', '3', '2', ports[13] = ['0', '5005076801301806', '5005076801101806', '3', '2',
'', '042200', 'no', 'no'] '', '042200', 'no', 'no', '']
ports[14] = ['0', '5005076801B91806', '5005076801101806', '3', '2', ports[14] = ['0', '5005076801B91806', '5005076801101806', '3', '2',
'', '042200', 'yes', 'yes'] '', '042200', 'yes', 'yes', '']
ports[15] = ['0', '5005076801401806', '5005076801101806', '4', '2', ports[15] = ['0', '5005076801401806', '5005076801101806', '4', '2',
'', '042200', 'no', 'no'] '', '042200', 'no', 'no', '']
ports[16] = ['0', '5005076801C91806', '5005076801101806', '4', '2', ports[16] = ['0', '5005076801C91806', '5005076801101806', '4', '2',
'', '042200', 'yes', 'yes'] '', '042200', 'yes', 'yes', '']
if 'filtervalue' in kwargs: if 'filtervalue' in kwargs:
rows = [] rows = []
rows.append(['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id', rows.append(['id', 'WWPN', 'WWNN', 'port_id', 'owning_node_id',
'current_node_id', 'nportid', 'host_io_permitted', 'current_node_id', 'nportid', 'host_io_permitted',
'virtualized']) 'virtualized', 'fc_io_port_id'])
if ':' in kwargs['filtervalue']: if ':' in kwargs['filtervalue']:
filter1 = kwargs['filtervalue'].split(':')[0] filter1 = kwargs['filtervalue'].split(':')[0]
@ -805,6 +807,73 @@ port_speed!N/A
rows = ports rows = ports
return self._print_info_cmd(rows=rows, **kwargs) 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 # Print mostly made-up stuff in the correct syntax
def _cmd_lsportip(self, **kwargs): def _cmd_lsportip(self, **kwargs):
if self._next_cmd_error['lsportip'] == 'ip_no_config': if self._next_cmd_error['lsportip'] == 'ip_no_config':
@ -1247,6 +1316,10 @@ port_speed!N/A
host_info['site_name'] = kwargs['site'].strip('\'\"') host_info['site_name'] = kwargs['site'].strip('\'\"')
else: else:
host_info['site_name'] = '' 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) out, err = self._add_port_to_host(host_info, **kwargs)
if not len(err): if not len(err):
self._hosts_list[host_name] = host_info self._hosts_list[host_name] = host_info
@ -4826,6 +4899,26 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.sim.error_injection('lsportip', 'remove_field') self.sim.error_injection('lsportip', 'remove_field')
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.VolumeBackendAPIException,
self.driver.do_setup, None) 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 # Check with bad parameters
self._set_flag('san_ip', '') self._set_flag('san_ip', '')
@ -4873,6 +4966,37 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Finally, check with good parameters # Finally, check with good parameters
self.driver.do_setup(None) 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(ssh_utils, 'SSHPool')
@mock.patch.object(processutils, 'ssh_execute') @mock.patch.object(processutils, 'ssh_execute')
def test_run_ssh_set_up_with_san_ip(self, mock_ssh_execute, mock_ssh_pool): 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, 'mirror_pool': None,
'volume_topology': None, 'volume_topology': None,
'peer_pool': None, 'peer_pool': None,
'storwize_portset': None,
'storwize_svc_src_child_pool': None, 'storwize_svc_src_child_pool': None,
'storwize_svc_target_child_pool': None, 'storwize_svc_target_child_pool': None,
'cycle_period_seconds': 300 'cycle_period_seconds': 300

View File

@ -133,6 +133,10 @@ storwize_svc_opts = [
default=None, default=None,
help='Specifies the name of the pool in which mirrored copy ' help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'), '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', cfg.StrOpt('storwize_svc_src_child_pool',
default=None, default=None,
help='Specifies the name of the source child pool in which ' help='Specifies the name of the source child pool in which '
@ -272,11 +276,13 @@ class StorwizeSSH(object):
port.append(port_name) port.append(port_name)
return port 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) port = self._create_port_arg(port_type, port_name)
ssh_cmd = ['svctask', 'mkhost', '-force'] + port ssh_cmd = ['svctask', 'mkhost', '-force'] + port
if site: if site:
ssh_cmd += ['-site', '"%s"' % site] ssh_cmd += ['-site', '"%s"' % site]
if portset:
ssh_cmd += ['-portset', '"%s"' % portset]
ssh_cmd += ['-name', '"%s"' % host_name] ssh_cmd += ['-name', '"%s"' % host_name]
return self.run_ssh_check_created(ssh_cmd) return self.run_ssh_check_created(ssh_cmd)
@ -318,6 +324,12 @@ class StorwizeSSH(object):
ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!'] ssh_cmd = ['svcinfo', 'lsiscsiauth', '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True) 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): def lsfabric(self, wwpn=None, host=None):
ssh_cmd = ['svcinfo', 'lsfabric', '-delim', '!'] ssh_cmd = ['svcinfo', 'lsfabric', '-delim', '!']
if wwpn: if wwpn:
@ -765,6 +777,10 @@ class StorwizeSSH(object):
ssh_cmd += ['-filtervalue', 'current_node_id=%s' % current_node_id] ssh_cmd += ['-filtervalue', 'current_node_id=%s' % current_node_id]
return self.run_ssh_info(ssh_cmd, with_header=True) 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'): def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool, '-copy', ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool, '-copy',
copy_id, '-vdisk', vdisk] copy_id, '-vdisk', vdisk]
@ -1070,6 +1086,7 @@ class StorwizeHelpers(object):
node['WWPN'] = [] node['WWPN'] = []
node['ipv4'] = [] node['ipv4'] = []
node['ipv6'] = [] node['ipv6'] = []
node['IP_address'] = []
node['enabled_protocols'] = [] node['enabled_protocols'] = []
nodes[node['id']] = node nodes[node['id']] = node
node['site_id'] = (node_data['site_id'] node['site_id'] = (node_data['site_id']
@ -1080,21 +1097,37 @@ class StorwizeHelpers(object):
self.handle_keyerror('lsnode', node_data) self.handle_keyerror('lsnode', node_data)
return nodes 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.""" """Add iSCSI IP addresses to system node information."""
resp = self.ssh.lsportip() if code_level >= (8, 4, 2, 0):
for ip_data in resp: portset_name = portset if portset else 'portset0'
try: lsip_resp = self.ssh.lsip(portset=portset_name)
state = ip_data['state'] for node_data in storage_nodes:
if ip_data['node_id'] in storage_nodes and ( ip_addresses = []
state == 'configured' or state == 'online'): try:
node = storage_nodes[ip_data['node_id']] for ip_data in lsip_resp:
if len(ip_data['IP_address']): if ip_data['node_id'] in node_data:
node['ipv4'].append(ip_data['IP_address']) if (ip_data['IP_address']):
if len(ip_data['IP_address_6']): ip_addresses.append(ip_data['IP_address'])
node['ipv6'].append(ip_data['IP_address_6']) except KeyError:
except KeyError: self.handle_keyerror('lsip', ip_data)
self.handle_keyerror('lsportip', 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 (
state == 'configured' or state == 'online'):
node = storage_nodes[ip_data['node_id']]
if len(ip_data['IP_address']):
node['ipv4'].append(ip_data['IP_address'])
if len(ip_data['IP_address_6']):
node['ipv6'].append(ip_data['IP_address_6'])
except KeyError:
self.handle_keyerror('lsportip', ip_data)
def add_fc_wwpns(self, storage_nodes, code_level): def add_fc_wwpns(self, storage_nodes, code_level):
"""Add FC WWPNs to system node information.""" """Add FC WWPNs to system node information."""
@ -1110,20 +1143,36 @@ class StorwizeHelpers(object):
port_info['status'] == 'active'): port_info['status'] == 'active'):
wwpns.add(port_info['WWPN']) wwpns.add(port_info['WWPN'])
else: 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) wwpns.update(npiv_wwpns)
node['WWPN'] = list(wwpns) node['WWPN'] = list(wwpns)
LOG.info('WWPN on node %(node)s: %(wwpn)s.', LOG.info('WWPN on node %(node)s: %(wwpn)s.',
{'node': node['id'], 'wwpn': node['WWPN']}) {'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() wwpns = set()
# In the response of lstargetportfc, the host_io_permitted # In the response of lstargetportfc, the host_io_permitted
# indicates whether the port can be used for host I/O # 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) host_io_permitted=host_io)
for port_info in resp: if code_level >= (8, 4, 2, 0):
wwpns.add(port_info['WWPN']) 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) return list(wwpns)
def add_chap_secret_to_host(self, host_name): def add_chap_secret_to_host(self, host_name):
@ -1288,7 +1337,7 @@ class StorwizeHelpers(object):
LOG.debug('Leave: get_host_from_connector: host %s.', host_name) LOG.debug('Leave: get_host_from_connector: host %s.', host_name)
return 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. """Create a new host on the storage system.
We create a host name and associate it with the given connection 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 # Create a host with one port
port = ports.pop(0) port = ports.pop(0)
# Host site_id is necessary for hyperswap volume. # 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 # Add any additional ports to the host
for port in ports: for port in ports:
@ -1514,6 +1563,7 @@ class StorwizeHelpers(object):
'mirror_pool': config.storwize_svc_mirror_pool, 'mirror_pool': config.storwize_svc_mirror_pool,
'volume_topology': None, 'volume_topology': None,
'peer_pool': config.storwize_peer_pool, 'peer_pool': config.storwize_peer_pool,
'storwize_portset': config.storwize_portset,
'storwize_svc_src_child_pool': 'storwize_svc_src_child_pool':
config.storwize_svc_src_child_pool, config.storwize_svc_src_child_pool,
'storwize_svc_target_child_pool': 'storwize_svc_target_child_pool':
@ -3279,14 +3329,15 @@ class StorwizeSVCCommonDriver(san.SanDriver,
state['storage_nodes'] = helper.get_node_info() state['storage_nodes'] = helper.get_node_info()
# Add the iSCSI IP addresses and WWPNs to the storage 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']) helper.add_fc_wwpns(state['storage_nodes'], state['code_level'])
# For each node, check what connection modes it supports. Delete any # For each node, check what connection modes it supports. Delete any
# nodes that do not support any types (may be partially configured). # nodes that do not support any types (may be partially configured).
to_delete = [] to_delete = []
for k, node in state['storage_nodes'].items(): 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'])): and len(node['iscsi_name'])):
node['enabled_protocols'].append('iSCSI') node['enabled_protocols'].append('iSCSI')
state['enabled_protocols'].add('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.3 - Add replication group support
2.2.4 - Add backup snapshots support 2.2.4 - Add backup snapshots support
2.2.5 - Add hyperswap 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 # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -190,7 +191,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
# this connector info. # this connector info.
host_name = None host_name = None
try: 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: except exception.VolumeBackendAPIException as excp:
if "CMMVC6035E" in excp.msg: if "CMMVC6035E" in excp.msg:
msg = (_('Host already exists for connector ' msg = (_('Host already exists for connector '
@ -266,8 +270,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
conn_wwpns.extend(node['WWPN']) conn_wwpns.extend(node['WWPN'])
else: else:
npiv_wwpns = backend_helper.get_npiv_wwpns( npiv_wwpns = backend_helper.get_npiv_wwpns(
node_state['code_level'],
node_id=node['id'], node_id=node['id'],
host_io="yes") host_io="yes",
portset=opts['storwize_portset'])
conn_wwpns.extend(npiv_wwpns) conn_wwpns.extend(npiv_wwpns)
properties['target_wwn'] = conn_wwpns properties['target_wwn'] = conn_wwpns
@ -423,8 +429,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
if node_state['code_level'] < (7, 7, 0, 0): if node_state['code_level'] < (7, 7, 0, 0):
conn_wwpns.extend(node['WWPN']) conn_wwpns.extend(node['WWPN'])
else: else:
npivwwpns = backend_helper.get_npiv_wwpns(node_id=node['id'], npivwwpns = (
host_io="yes") backend_helper.get_npiv_wwpns(node_state['code_level'],
node_id=node['id'],
host_io="yes"))
conn_wwpns.extend(npivwwpns) conn_wwpns.extend(npivwwpns)
i_t_map = self._make_initiator_target_map(connector['wwpns'], 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.2 - Add replication group support
2.2.3 - Add backup snapshots support 2.2.3 - Add backup snapshots support
2.2.4 - Add hyperswap 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 # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"
@ -183,8 +184,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# this connector info. # this connector info.
host_name = None host_name = None
try: try:
host_name = backend_helper.create_host(connector, iscsi=True, opts = self._get_vdisk_params(volume.volume_type_id)
site=host_site) host_name = (
backend_helper.create_host(connector, iscsi=True,
site=host_site,
portset=opts['storwize_portset']))
except exception.VolumeBackendAPIException as excp: except exception.VolumeBackendAPIException as excp:
if "CMMVC6578E" in excp.msg: if "CMMVC6578E" in excp.msg:
msg = (_('Host already exists for connector ' msg = (_('Host already exists for connector '
@ -213,13 +217,15 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
try: try:
properties = self._get_single_iscsi_data(volume, connector, properties = self._get_single_iscsi_data(volume, connector,
lun_id, chap_secret) lun_id, chap_secret,
opts['storwize_portset'])
multipath = connector.get('multipath', False) multipath = connector.get('multipath', False)
if multipath: if multipath:
properties = self._get_multi_iscsi_data(volume, connector, properties = (
lun_id, properties, self._get_multi_iscsi_data(volume, connector,
backend_helper, lun_id, properties,
node_state) backend_helper, node_state,
opts['storwize_portset']))
except Exception as ex: except Exception as ex:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.error('initialize_connection: Failed to export volume ' LOG.error('initialize_connection: Failed to export volume '
@ -240,7 +246,8 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
return {'driver_volume_type': 'iscsi', 'data': properties, } 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 ' LOG.debug('enter: _get_single_iscsi_data: volume %(vol)s with '
'connector %(conn)s lun_id %(lun_id)s', 'connector %(conn)s lun_id %(lun_id)s',
{'vol': volume.id, 'conn': connector, {'vol': volume.id, 'conn': connector,
@ -277,6 +284,11 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
# Get preferred node and other nodes in I/O group # Get preferred node and other nodes in I/O group
preferred_node_entry = None preferred_node_entry = None
io_group_nodes = [] 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(): for node in node_state['storage_nodes'].values():
if self.protocol not in node['enabled_protocols']: if self.protocol not in node['enabled_protocols']:
continue continue
@ -305,10 +317,14 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
'target_lun': lun_id, 'target_lun': lun_id,
'volume_id': volume.id} 'volume_id': volume.id}
if preferred_node_entry['ipv4']: if node_state['code_level'] >= (8, 4, 2, 0):
ipaddr = preferred_node_entry['ipv4'][0] if preferred_node_entry['IP_address']:
ipaddr = preferred_node_entry['IP_address'][0]
else: else:
ipaddr = preferred_node_entry['ipv6'][0] if preferred_node_entry['ipv4']:
ipaddr = preferred_node_entry['ipv4'][0]
else:
ipaddr = preferred_node_entry['ipv6'][0]
properties['target_portal'] = '%s:%s' % (ipaddr, '3260') properties['target_portal'] = '%s:%s' % (ipaddr, '3260')
properties['target_iqn'] = preferred_node_entry['iscsi_name'] properties['target_iqn'] = preferred_node_entry['iscsi_name']
if chap_secret: if chap_secret:
@ -327,14 +343,18 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
return properties return properties
def _get_multi_iscsi_data(self, volume, connector, lun_id, 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 ' LOG.debug('enter: _get_multi_iscsi_data: volume %(vol)s with '
'connector %(conn)s lun_id %(lun_id)s', 'connector %(conn)s lun_id %(lun_id)s',
{'vol': volume.id, 'conn': connector, {'vol': volume.id, 'conn': connector,
'lun_id': lun_id}) 'lun_id': lun_id})
try: try:
resp = backend_helper.ssh.lsportip() 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: except Exception as ex:
msg = (_('_get_multi_iscsi_data: Failed to ' msg = (_('_get_multi_iscsi_data: Failed to '
'get port ip because of exception: ' 'get port ip because of exception: '
@ -351,11 +371,14 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
continue continue
link_state = ip_data.get('link_state', None) link_state = ip_data.get('link_state', None)
valid_port = '' valid_port = ''
if ((ip_data['state'] == 'configured' and if node_state['code_level'] >= (8, 4, 2, 0):
link_state == 'active') or valid_port = ip_data['IP_address']
ip_data['state'] == 'online'): else:
valid_port = (ip_data['IP_address'] or if ((ip_data['state'] == 'configured' and
ip_data['IP_address_6']) link_state == 'active') or
ip_data['state'] == 'online'):
valid_port = (ip_data['IP_address'] or
ip_data['IP_address_6'])
if valid_port: if valid_port:
properties['target_portals'].append( properties['target_portals'].append(
'%s:%s' % (valid_port, '3260')) '%s:%s' % (valid_port, '3260'))

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