'target_iqn':
'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
'target_lun': 2,
- 'target_portal': '10.244.214.118:3260'},
+ 'target_portal': '10.244.214.118:3260',
+ 'volume_id': '1'},
'driver_volume_type': 'iscsi'}
iscsi_connection_info_rw = \
'target_iqn':
'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
'target_lun': 2,
- 'target_portal': '10.244.214.118:3260'},
+ 'target_portal': '10.244.214.118:3260',
+ 'volume_id': '1'},
+ 'driver_volume_type': 'iscsi'}
+
+ iscsi_connection_info_mp = \
+ {'data': {'access_mode': 'rw',
+ 'target_discovered': True,
+ 'target_iqns': [
+ 'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+ 'iqn.1992-04.com.emc:cx.fnm00124000215.a5'],
+ 'target_luns': [2, 2],
+ 'target_portals': [
+ '10.244.214.118:3260',
+ '10.244.214.119:3260'],
+ 'volume_id': '1'},
'driver_volume_type': 'iscsi'}
PING_OK = ("Reply from 10.0.0.2: bytes=32 time=1ms TTL=30\n" +
1 1
Shareable: YES""" % sgname, 0)
+ def STORAGE_GROUP_HAS_MAP_MP(self, sgname):
+
+ return ("""\
+ Storage Group Name: %s
+ Storage Group UID: 54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D
+ HBA/SP Pairs:
+
+ HBA UID SP Name SPPort
+ ------- ------- ------
+ iqn.1993-08.org.debian:01:222 SP A 4
+ iqn.1993-08.org.debian:01:222 SP A 5
+
+ HLU/ALU Pairs:
+
+ HLU Number ALU Number
+ ---------- ----------
+ 1 1
+ Shareable: YES""" % sgname, 0)
+
def STORAGE_GROUP_HAS_MAP_2(self, sgname):
return ("""\
"volume backend name is not correct")
self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
self.assertTrue(
- stats['driver_version'] == "05.00.00",
+ stats['driver_version'] == "05.01.00",
"driver version is incorrect.")
def test_get_volume_stats_too_many_luns(self):
'10.0.0.2'))]
fake_cli.assert_has_calls(expected)
+ @mock.patch('random.randint',
+ mock.Mock(return_value=0))
+ def test_initialize_connection_multipath(self):
+ self.configuration.initiator_auto_registration = False
+
+ commands = [('storagegroup', '-list', '-gname', 'fakehost')]
+ results = [self.testData.STORAGE_GROUP_HAS_MAP_MP('fakehost')]
+ fake_cli = self.driverSetup(commands, results)
+ self.driver.cli.iscsi_targets = {
+ 'A': [
+ {'Port WWN': 'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+ 'SP': 'A',
+ 'Port ID': 4,
+ 'Virtual Port ID': 0,
+ 'IP Address': '10.244.214.118'},
+ {'Port WWN': 'iqn.1992-04.com.emc:cx.fnm00124000215.a5',
+ 'SP': 'A',
+ 'Port ID': 5,
+ 'Virtual Port ID': 1,
+ 'IP Address': '10.244.214.119'}],
+ 'B': []}
+ test_volume_rw = self.testData.test_volume_rw.copy()
+ test_volume_rw['provider_location'] = 'system^fakesn|type^lun|id^1'
+ connector_m = dict(self.testData.connector)
+ connector_m['multipath'] = True
+ connection_info = self.driver.initialize_connection(
+ test_volume_rw,
+ connector_m)
+
+ self.assertEqual(connection_info,
+ self.testData.iscsi_connection_info_mp)
+
+ expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost',
+ poll=False),
+ mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1,
+ '-gname', 'fakehost', poll=False),
+ mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'),
+ poll=False)]
+ fake_cli.assert_has_calls(expected)
+
@mock.patch(
"oslo_concurrency.processutils.execute",
mock.Mock(
"volume backend name is not correct")
self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
self.assertTrue(
- stats['driver_version'] == "05.00.00",
+ stats['driver_version'] == "05.01.00",
"driver version is incorrect.")
def test_get_volume_stats_too_many_luns(self):
Initiator Auto Deregistration,
Force Deleting LUN in Storage Groups,
robust enhancement
+ 5.1.0 - iSCSI multipath enhancement
"""
def __init__(self, *args, **kwargs):
Initiator Auto Deregistration,
Force Deleting LUN in Storage Groups,
robust enhancement
+ 5.1.0 - iSCSI multipath enhancement
"""
def __init__(self, *args, **kwargs):
The iscsi driver returns a driver_volume_type of 'iscsi'.
the format of the driver data is defined in vnx_get_iscsi_properties.
- Example return value::
+ Example return value (multipath is not enabled)::
{
'driver_volume_type': 'iscsi'
}
}
+ Example return value (multipath is enabled)::
+
+ {
+ 'driver_volume_type': 'iscsi'
+ 'data': {
+ 'target_discovered': True,
+ 'target_iqns': ['iqn.2010-10.org.openstack:volume-00001',
+ 'iqn.2010-10.org.openstack:volume-00002'],
+ 'target_portals': ['127.0.0.1:3260', '127.0.1.1:3260'],
+ 'target_luns': [1, 1],
+ 'access_mode': 'rw'
+ }
+ }
+
"""
return self.cli.initialize_connection(volume, connector)
connection_pingnode)
return False
- def find_avaialable_iscsi_target_one(self, hostname,
- preferred_sp,
- registered_spport_set,
- all_iscsi_targets):
+ def find_available_iscsi_targets(self, hostname,
+ preferred_sp,
+ registered_spport_set,
+ all_iscsi_targets,
+ multipath=False):
if self.iscsi_initiator_map and hostname in self.iscsi_initiator_map:
iscsi_initiator_ips = list(self.iscsi_initiator_map[hostname])
random.shuffle(iscsi_initiator_ips)
else:
target_sps = ('B', 'A')
+ if multipath:
+ target_portals = []
+ for target_sp in target_sps:
+ sp_portals = all_iscsi_targets[target_sp]
+ for portal in sp_portals:
+ spport = (portal['SP'], portal['Port ID'])
+ if spport not in registered_spport_set:
+ LOG.debug("Skip SP Port %(port)s since "
+ "no path from %(host)s is through it",
+ {'port': spport,
+ 'host': hostname})
+ continue
+ target_portals.append(portal)
+ return target_portals
+
for target_sp in target_sps:
target_portals = list(all_iscsi_targets[target_sp])
random.shuffle(target_portals)
if iscsi_initiator_ips is not None:
for initiator_ip in iscsi_initiator_ips:
if self.ping_node(target_portal, initiator_ip):
- return target_portal
+ return [target_portal]
else:
LOG.debug("No iSCSI IP address of %(hostname)s is known. "
"Return a random target portal %(portal)s.",
class EMCVnxCliBase(object):
"""This class defines the functions to use the native CLI functionality."""
- VERSION = '05.00.00'
+ VERSION = '05.01.00'
stats = {'driver_version': VERSION,
'free_capacity_gb': 'unknown',
'reserved_percentage': 0,
def vnx_get_iscsi_properties(self, volume, connector, hlu, sg_raw_output):
storage_group = connector['host']
+ multipath = connector.get('multipath', False)
owner_sp = self.get_lun_owner(volume)
registered_spports = self._client.get_registered_spport_set(
connector['initiator'],
storage_group,
sg_raw_output)
- target = self._client.find_avaialable_iscsi_target_one(
+ targets = self._client.find_available_iscsi_targets(
storage_group, owner_sp,
registered_spports,
- self.iscsi_targets)
- properties = {'target_discovered': True,
- 'target_iqn': 'unknown',
- 'target_portal': 'unknown',
- 'target_lun': 'unknown',
- 'volume_id': volume['id']}
- if target:
- properties = {'target_discovered': True,
- 'target_iqn': target['Port WWN'],
- 'target_portal': "%s:3260" % target['IP Address'],
- 'target_lun': hlu}
- LOG.debug("iSCSI Properties: %s", properties)
- auth = volume['provider_auth']
- if auth:
- (auth_method, auth_username, auth_secret) = auth.split()
- properties['auth_method'] = auth_method
- properties['auth_username'] = auth_username
- properties['auth_password'] = auth_secret
+ self.iscsi_targets,
+ multipath)
+
+ properties = {}
+
+ if not multipath:
+ properties = {'target_discovered': False,
+ 'target_iqn': 'unknown',
+ 'target_portal': 'unknown',
+ 'target_lun': 'unknown',
+ 'volume_id': volume['id']}
+ if targets:
+ properties['target_discovered'] = True
+ properties['target_iqn'] = targets[0]['Port WWN']
+ properties['target_portal'] = \
+ "%s:3260" % targets[0]['IP Address']
+ properties['target_lun'] = hlu
+
+ auth = volume['provider_auth']
+ if auth:
+ (auth_method, auth_username, auth_secret) = auth.split()
+ properties['auth_method'] = auth_method
+ properties['auth_username'] = auth_username
+ properties['auth_password'] = auth_secret
else:
- LOG.error(_LE('Failed to find an available iSCSI targets for %s.'),
+ properties = {'target_discovered': False,
+ 'target_iqns': None,
+ 'target_portals': None,
+ 'target_luns': None,
+ 'volume_id': volume['id']}
+ if targets:
+ properties['target_discovered'] = True
+ properties['target_iqns'] = [t['Port WWN'] for t in targets]
+ properties['target_portals'] = [
+ "%s:3260" % t['IP Address'] for t in targets]
+ properties['target_luns'] = [hlu] * len(targets)
+
+ if not targets:
+ LOG.error(_LE('Failed to find available iSCSI targets for %s.'),
storage_group)
return properties