3PAR iSCSI volumes attach to single nsp
Update the 3PAR iSCSI Driver to correctly identify the least used iSCSI Node:Slot:Port (NSP) and only attach the volume using that single NSP. DocImpact: Driver now requires 3PAR firmware version 3.1.2 MU3 Ref: https://review.openstack.org/#/c/68513/ Change-Id: I1627e44a5168a7dbd19ea62312d65a00044753e9 Closes-Bug: #1269515
This commit is contained in:
parent
4325ccae28
commit
99379d6ec0
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# (c) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -481,8 +481,8 @@ class HP3PARBaseDriver():
|
|||
else:
|
||||
del self._hosts[hostname]
|
||||
|
||||
def fake_create_3par_vlun(self, volume, hostname):
|
||||
self.driver.common.client.createVLUN(volume, 19, hostname)
|
||||
def fake_create_3par_vlun(self, volume, hostname, nsp):
|
||||
self.driver.common.client.createVLUN(volume, 19, hostname, nsp)
|
||||
|
||||
def fake_get_ports(self):
|
||||
ports = self.FAKE_FC_PORTS
|
||||
|
@ -1233,52 +1233,90 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
|
|||
ports = self.driver.common.get_ports()['members']
|
||||
self.assertEqual(len(ports), 3)
|
||||
|
||||
def test_get_iscsi_ip_active(self):
|
||||
def test_get_least_used_nsp_for_host_single(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
|
||||
#record set up
|
||||
self.clear_mox()
|
||||
|
||||
getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts)
|
||||
self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts)
|
||||
self.driver.common.client.getPorts = mock.Mock(
|
||||
return_value=PORTS_RET)
|
||||
|
||||
getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs)
|
||||
self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs)
|
||||
self.driver.common.client.getVLUNs = mock.Mock(
|
||||
return_value=VLUNS1_RET)
|
||||
|
||||
getPorts().AndReturn(PORTS_RET)
|
||||
getVLUNs().AndReturn(VLUNS2_RET)
|
||||
self.mox.ReplayAll()
|
||||
#Setup a single ISCSI IP
|
||||
iscsi_ips = ["10.10.220.253"]
|
||||
self.driver.configuration.hp3par_iscsi_ips = iscsi_ips
|
||||
|
||||
config = self.setup_configuration()
|
||||
config.hp3par_iscsi_ips = ['10.10.220.253', '10.10.220.252']
|
||||
self.setup_driver(config, set_up_fakes=False)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.initialize_iscsi_ports()
|
||||
|
||||
ip = self.driver._get_iscsi_ip('fakehost')
|
||||
self.assertEqual(ip, '10.10.220.252')
|
||||
nsp = self.driver._get_least_used_nsp_for_host('newhost')
|
||||
self.assertEqual(nsp, "1:8:1")
|
||||
|
||||
def test_get_iscsi_ip(self):
|
||||
def test_get_least_used_nsp_for_host_new(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
|
||||
#record driver set up
|
||||
self.clear_mox()
|
||||
getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts)
|
||||
self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts)
|
||||
|
||||
getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs)
|
||||
self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs)
|
||||
self.driver.common.client.getPorts = mock.Mock(
|
||||
return_value=PORTS_RET)
|
||||
|
||||
getPorts().AndReturn(PORTS_RET)
|
||||
getVLUNs().AndReturn(VLUNS1_RET)
|
||||
self.mox.ReplayAll()
|
||||
self.driver.common.client.getVLUNs = mock.Mock(
|
||||
return_value=VLUNS1_RET)
|
||||
|
||||
config = self.setup_configuration()
|
||||
config.iscsi_ip_address = '10.10.10.10'
|
||||
config.hp3par_iscsi_ips = ['10.10.220.253', '10.10.220.252']
|
||||
self.setup_driver(config, set_up_fakes=False)
|
||||
#Setup two ISCSI IPs
|
||||
iscsi_ips = ["10.10.220.252", "10.10.220.253"]
|
||||
self.driver.configuration.hp3par_iscsi_ips = iscsi_ips
|
||||
|
||||
ip = self.driver._get_iscsi_ip('fakehost')
|
||||
self.assertEqual(ip, '10.10.220.252')
|
||||
self.driver.initialize_iscsi_ports()
|
||||
|
||||
# Host 'newhost' does not yet have any iscsi paths,
|
||||
# so the 'least used' is returned
|
||||
nsp = self.driver._get_least_used_nsp_for_host('newhost')
|
||||
self.assertEqual(nsp, "1:8:2")
|
||||
|
||||
def test_get_least_used_nsp_for_host_reuse(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
self.clear_mox()
|
||||
|
||||
self.driver.common.client.getPorts = mock.Mock(
|
||||
return_value=PORTS_RET)
|
||||
|
||||
self.driver.common.client.getVLUNs = mock.Mock(
|
||||
return_value=VLUNS1_RET)
|
||||
|
||||
#Setup two ISCSI IPs
|
||||
iscsi_ips = ["10.10.220.252", "10.10.220.253"]
|
||||
self.driver.configuration.hp3par_iscsi_ips = iscsi_ips
|
||||
|
||||
self.driver.initialize_iscsi_ports()
|
||||
|
||||
# hosts 'foo' and 'bar' already have active iscsi paths
|
||||
# the same one should be used
|
||||
nsp = self.driver._get_least_used_nsp_for_host('foo')
|
||||
self.assertEqual(nsp, "1:8:2")
|
||||
|
||||
nsp = self.driver._get_least_used_nsp_for_host('bar')
|
||||
self.assertEqual(nsp, "1:8:1")
|
||||
|
||||
def test_get_least_used_nps_for_host_fc(self):
|
||||
# Ensure that with an active FC path setup
|
||||
# an ISCSI path is used
|
||||
self.flags(lock_path=self.tempdir)
|
||||
self.clear_mox()
|
||||
|
||||
self.driver.common.client.getPorts = mock.Mock(
|
||||
return_value=PORTS1_RET)
|
||||
self.driver.common.client.getVLUNs = mock.Mock(
|
||||
return_value=VLUNS5_RET)
|
||||
|
||||
#Setup two ISCSI IPs
|
||||
iscsi_ips = ["10.10.220.252", "10.10.220.253"]
|
||||
self.driver.configuration.hp3par_iscsi_ips = iscsi_ips
|
||||
|
||||
self.driver.initialize_iscsi_ports()
|
||||
|
||||
nsp = self.driver._get_least_used_nsp_for_host('newhost')
|
||||
self.assertNotEqual(nsp, "0:6:3")
|
||||
self.assertEqual(nsp, "1:8:1")
|
||||
|
||||
def test_invalid_iscsi_ip(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
|
@ -1443,3 +1481,8 @@ VLUNS4_RET = ({'members':
|
|||
'active': True},
|
||||
{'portPos': {'node': 0, 'slot': 2, 'cardPort': 1},
|
||||
'active': True}]})
|
||||
VLUNS5_RET = ({'members':
|
||||
[{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2},
|
||||
'active': True},
|
||||
{'portPos': {'node': 1, 'slot': 8, 'cardPort': 1},
|
||||
'active': True}]})
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# (c) Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
|
||||
# (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -18,7 +16,7 @@
|
|||
"""
|
||||
Volume driver common utilities for HP 3PAR Storage array
|
||||
|
||||
The 3PAR drivers requires 3.1.2 MU2 firmware on the 3PAR array.
|
||||
The 3PAR drivers requires 3.1.2 MU3 firmware on the 3PAR array.
|
||||
|
||||
You will need to install the python hp3parclient.
|
||||
sudo pip install hp3parclient
|
||||
|
@ -113,13 +111,22 @@ class HP3PARCommon(object):
|
|||
1.2.3 - Methods to update key/value pair bug #1258033
|
||||
1.2.4 - Remove deprecated config option hp3par_domain
|
||||
1.2.5 - Raise Ex when deleting snapshot with dependencies bug #1250249
|
||||
1.2.6 - Allow optional specifying n:s:p for vlun creation bug #1269515
|
||||
This update now requires 3.1.2 MU3 firmware
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.2.5"
|
||||
VERSION = "1.2.6"
|
||||
|
||||
stats = {}
|
||||
|
||||
# TODO(Ramy): move these to the 3PAR Client
|
||||
VLUN_TYPE_EMPTY = 1
|
||||
VLUN_TYPE_PORT = 2
|
||||
VLUN_TYPE_HOST = 3
|
||||
VLUN_TYPE_MATCHED_SET = 4
|
||||
VLUN_TYPE_HOST_SET = 5
|
||||
|
||||
# Valid values for volume type extra specs
|
||||
# The first value in the list is the default value
|
||||
valid_prov_values = ['thin', 'full']
|
||||
|
@ -385,9 +392,15 @@ exit
|
|||
def _delete_3par_host(self, hostname):
|
||||
self.client.deleteHost(hostname)
|
||||
|
||||
def _create_3par_vlun(self, volume, hostname):
|
||||
def _create_3par_vlun(self, volume, hostname, nsp):
|
||||
try:
|
||||
self.client.createVLUN(volume, hostname=hostname, auto=True)
|
||||
if nsp is None:
|
||||
self.client.createVLUN(volume, hostname=hostname, auto=True)
|
||||
else:
|
||||
port = self.build_portPos(nsp)
|
||||
self.client.createVLUN(volume, hostname=hostname, auto=True,
|
||||
portPos=port)
|
||||
|
||||
except hpexceptions.HTTPBadRequest as e:
|
||||
if 'must be in the same domain' in e.get_description():
|
||||
LOG.error(e.get_description())
|
||||
|
@ -485,19 +498,25 @@ exit
|
|||
|
||||
self.stats = stats
|
||||
|
||||
def create_vlun(self, volume, host):
|
||||
def create_vlun(self, volume, host, nsp=None):
|
||||
"""Create a VLUN.
|
||||
|
||||
In order to export a volume on a 3PAR box, we have to create a VLUN.
|
||||
"""
|
||||
volume_name = self._get_3par_vol_name(volume['id'])
|
||||
self._create_3par_vlun(volume_name, host['name'])
|
||||
self._create_3par_vlun(volume_name, host['name'], nsp)
|
||||
return self.client.getVLUN(volume_name)
|
||||
|
||||
def delete_vlun(self, volume, hostname):
|
||||
volume_name = self._get_3par_vol_name(volume['id'])
|
||||
vlun = self.client.getVLUN(volume_name)
|
||||
self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
|
||||
# VLUN Type of MATCHED_SET 4 requires the port to be provided
|
||||
if self.VLUN_TYPE_MATCHED_SET == vlun['type']:
|
||||
self.client.deleteVLUN(volume_name, vlun['lun'], hostname,
|
||||
vlun['portPos'])
|
||||
else:
|
||||
self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
|
||||
|
||||
try:
|
||||
self._delete_3par_host(hostname)
|
||||
except hpexceptions.HTTPConflict as ex:
|
||||
|
@ -1095,3 +1114,11 @@ exit
|
|||
return '%s:%s:%s' % (portPos['node'],
|
||||
portPos['slot'],
|
||||
portPos['cardPort'])
|
||||
|
||||
def build_portPos(self, nsp):
|
||||
split = nsp.split(":")
|
||||
portPos = {}
|
||||
portPos['node'] = int(split[0])
|
||||
portPos['slot'] = int(split[1])
|
||||
portPos['cardPort'] = int(split[2])
|
||||
return portPos
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# (c) Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
|
||||
# (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -17,7 +15,7 @@
|
|||
#
|
||||
"""
|
||||
Volume driver for HP 3PAR Storage array.
|
||||
This driver requires 3.1.2 MU2 firmware on the 3PAR array, using
|
||||
This driver requires 3.1.2 MU3 firmware on the 3PAR array, using
|
||||
the 2.x version of the hp3parclient.
|
||||
|
||||
You will need to install the python hp3parclient.
|
||||
|
@ -58,10 +56,12 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
|
|||
1.2.3 - log exceptions before raising
|
||||
1.2.4 - Fixed iSCSI active path bug #1224594
|
||||
1.2.5 - Added metadata during attach/detach bug #1258033
|
||||
1.2.6 - Use least-used iscsi n:s:p for iscsi volume attach bug #1269515
|
||||
This update now requires 3.1.2 MU3 firmware
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.2.5"
|
||||
VERSION = "1.2.6"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HP3PARISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -257,11 +257,18 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
|
|||
|
||||
# we have to make sure we have a host
|
||||
host = self._create_host(volume, connector)
|
||||
least_used_nsp = self._get_least_used_nsp_for_host(host['name'])
|
||||
|
||||
# now that we have a host, create the VLUN
|
||||
vlun = self.common.create_vlun(volume, host)
|
||||
vlun = self.common.create_vlun(volume, host, least_used_nsp)
|
||||
|
||||
iscsi_ip = self._get_iscsi_ip(host['name'])
|
||||
if least_used_nsp is None:
|
||||
msg = _("Least busy iSCSI port not found, "
|
||||
"using first iSCSI port in list.")
|
||||
LOG.warn(msg)
|
||||
iscsi_ip = self.iscsi_ips.keys()[0]
|
||||
else:
|
||||
iscsi_ip = self._get_ip_using_nsp(least_used_nsp)
|
||||
|
||||
iscsi_ip_port = self.iscsi_ips[iscsi_ip]['ip_port']
|
||||
iscsi_target_iqn = self.iscsi_ips[iscsi_ip]['iqn']
|
||||
|
@ -348,38 +355,34 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
|
|||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def _get_iscsi_ip(self, hostname):
|
||||
"""Get an iSCSI IP address to use.
|
||||
def _get_least_used_nsp_for_host(self, hostname):
|
||||
"""Get the least used NSP for the current host.
|
||||
|
||||
Steps to determine which IP address to use.
|
||||
* If only one IP address, return it
|
||||
* If there is an active vlun, return the IP associated with it
|
||||
* Return IP with fewest active vluns
|
||||
Steps to determine which NSP to use.
|
||||
* If only one iSCSI NSP, return it
|
||||
* If there is already an active vlun to this host, return its NSP
|
||||
* Return NSP with fewest active vluns
|
||||
"""
|
||||
if len(self.iscsi_ips) == 1:
|
||||
return self.iscsi_ips.keys()[0]
|
||||
|
||||
iscsi_nsps = self._get_iscsi_nsps()
|
||||
# If there's only one path, use it
|
||||
if len(iscsi_nsps) == 1:
|
||||
return iscsi_nsps[0]
|
||||
|
||||
# Try to reuse an existing iscsi path to the host
|
||||
vluns = self.common.client.getVLUNs()
|
||||
# see if there is already a path to the
|
||||
# host, if so use it
|
||||
for vlun in vluns['members']:
|
||||
if vlun['active']:
|
||||
if vlun['hostname'] == hostname:
|
||||
# this host already has a path, so use it
|
||||
nsp = self.common.build_nsp(vlun['portPos'])
|
||||
return self._get_ip_using_nsp(nsp)
|
||||
temp_nsp = self.common.build_nsp(vlun['portPos'])
|
||||
if temp_nsp in iscsi_nsps:
|
||||
# this host already has an iscsi path, so use it
|
||||
return temp_nsp
|
||||
|
||||
# no current path find least used port
|
||||
# Calculate the least used iscsi nsp
|
||||
least_used_nsp = self._get_least_used_nsp(vluns['members'],
|
||||
self._get_iscsi_nsps())
|
||||
|
||||
if least_used_nsp is None:
|
||||
msg = _("Least busy iSCSI port not found, "
|
||||
"using first iSCSI port in list.")
|
||||
LOG.warn(msg)
|
||||
return self.iscsi_ips.keys()[0]
|
||||
|
||||
return self._get_ip_using_nsp(least_used_nsp)
|
||||
return least_used_nsp
|
||||
|
||||
def _get_iscsi_nsps(self):
|
||||
"""Return the list of candidate nsps."""
|
||||
|
@ -389,7 +392,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
|
|||
return nsps
|
||||
|
||||
def _get_ip_using_nsp(self, nsp):
|
||||
"""Return IP assiciated with given nsp."""
|
||||
"""Return IP associated with given nsp."""
|
||||
for (key, value) in self.iscsi_ips.items():
|
||||
if value['nsp'] == nsp:
|
||||
return key
|
||||
|
|
Loading…
Reference in New Issue