Adds multiple iSCSI port support to 3PAR

Added support to the 3PAR iSCSI OpenStack driver to provide the
ability to select the best fit target iSCSI port from a list of
candidate ports. The first time a volume is attached to a host,
all iSCSI ports configured for driver selection, are examined for
best fit. The port with the least active volumes attached will
then be selected as the path to the 3PAR array. Any subsequent
volume attach, to the same host, will use the established target
port.

DocImpact

Fixes bug #1197036

Change-Id: Icf8c28ea3f201e5e21c9a6ed00a2fbdda445c8b3
This commit is contained in:
Jim Branen
2013-07-10 12:34:09 -07:00
parent 2f5e26a247
commit d9e2c642e2
4 changed files with 424 additions and 134 deletions

View File

@@ -305,7 +305,10 @@ class HP3PARBaseDriver():
VOLUME_ID_SNAP = '761fc5e5-5191-4ec7-aeba-33e36de44156'
FAKE_DESC = 'test description name'
FAKE_FC_PORTS = ['0987654321234', '123456789000987']
FAKE_ISCSI_PORTS = ['10.10.10.10', '10.10.10.11']
FAKE_ISCSI_PORTS = {'1.1.1.2': {'nsp': '8:1:1',
'iqn': ('iqn.2000-05.com.3pardata:'
'21810002ac00383d'),
'ip_port': '3262'}}
volume = {'name': VOLUME_NAME,
'id': VOLUME_ID,
@@ -333,6 +336,43 @@ class HP3PARBaseDriver():
'wwnns': ["223456789012345", "223456789054321"],
'host': 'fakehost'}
def setup_configuration(self):
configuration = mox.MockObject(conf.Configuration)
configuration.hp3par_debug = False
configuration.hp3par_username = 'testUser'
configuration.hp3par_password = 'testPassword'
configuration.hp3par_api_url = 'https://1.1.1.1/api/v1'
configuration.hp3par_domain = HP3PAR_DOMAIN
configuration.hp3par_cpg = HP3PAR_CPG
configuration.hp3par_cpg_snap = HP3PAR_CPG_SNAP
configuration.iscsi_ip_address = '1.1.1.2'
configuration.iscsi_port = '1234'
configuration.san_ip = '2.2.2.2'
configuration.san_login = 'test'
configuration.san_password = 'test'
configuration.hp3par_snapshot_expiration = ""
configuration.hp3par_snapshot_retention = ""
configuration.hp3par_iscsi_ips = []
return configuration
def setup_fakes(self):
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client",
self.fake_create_client)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_get_3par_host",
self.fake_get_3par_host)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_delete_3par_host",
self.fake_delete_3par_host)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_3par_vlun",
self.fake_create_3par_vlun)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_ports",
self.fake_get_ports)
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
def clear_mox(self):
self.mox.ResetAll()
self.stubs.UnsetAll()
def fake_create_client(self):
return FakeHP3ParClient(self.driver.configuration.hp3par_api_url)
@@ -430,47 +470,26 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
super(TestHP3PARFCDriver, self).setUp()
self.setup_driver(self.setup_configuration())
self.setup_fakes()
configuration = mox.MockObject(conf.Configuration)
configuration.hp3par_debug = False
configuration.hp3par_username = 'testUser'
configuration.hp3par_password = 'testPassword'
configuration.hp3par_api_url = 'https://1.1.1.1/api/v1'
configuration.hp3par_cpg = HP3PAR_CPG
configuration.hp3par_cpg_snap = HP3PAR_CPG_SNAP
configuration.iscsi_ip_address = '1.1.1.2'
configuration.iscsi_port = '1234'
configuration.san_ip = '2.2.2.2'
configuration.san_login = 'test'
configuration.san_password = 'test'
configuration.hp3par_snapshot_expiration = ""
configuration.hp3par_snapshot_retention = ""
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "_create_client",
self.fake_create_client)
def setup_fakes(self):
super(TestHP3PARFCDriver, self).setup_fakes()
self.stubs.Set(hpfcdriver.HP3PARFCDriver,
"_create_3par_fibrechan_host",
self.fake_create_3par_fibrechan_host)
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "_get_3par_host",
self.fake_get_3par_host)
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "_delete_3par_host",
self.fake_delete_3par_host)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_3par_vlun",
self.fake_create_3par_vlun)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_ports",
self.fake_get_ports)
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
self.configuration = configuration
self.driver = hpfcdriver.HP3PARFCDriver(configuration=configuration)
self.driver.do_setup(None)
def tearDown(self):
shutil.rmtree(self.tempdir)
super(TestHP3PARFCDriver, self).tearDown()
def setup_driver(self, configuration):
self.driver = hpfcdriver.HP3PARFCDriver(configuration=configuration)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client",
self.fake_create_client)
self.driver.do_setup(None)
def fake_create_3par_fibrechan_host(self, hostname, wwn,
domain, persona_id):
host = {'FCPaths': [{'driverVersion': None,
@@ -577,7 +596,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -600,7 +619,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -627,7 +646,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -657,49 +676,19 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
super(TestHP3PARISCSIDriver, self).setUp()
self.setup_driver(self.setup_configuration())
self.setup_fakes()
configuration = mox.MockObject(conf.Configuration)
configuration.hp3par_debug = False
configuration.hp3par_username = 'testUser'
configuration.hp3par_password = 'testPassword'
configuration.hp3par_api_url = 'https://1.1.1.1/api/v1'
configuration.hp3par_cpg = HP3PAR_CPG
configuration.hp3par_cpg_snap = HP3PAR_CPG_SNAP
configuration.iscsi_ip_address = '1.1.1.2'
configuration.iscsi_port = '1234'
configuration.san_ip = '2.2.2.2'
configuration.san_login = 'test'
configuration.san_password = 'test'
configuration.hp3par_snapshot_expiration = ""
configuration.hp3par_snapshot_retention = ""
def setup_fakes(self):
super(TestHP3PARISCSIDriver, self).setup_fakes()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client",
self.fake_create_client)
self.stubs.Set(hpdriver.HP3PARISCSIDriver,
"_iscsi_discover_target_iqn",
self.fake_iscsi_discover_target_iqn)
self.stubs.Set(hpdriver.HP3PARISCSIDriver, "_create_3par_iscsi_host",
self.fake_create_3par_iscsi_host)
self.stubs.Set(hpdriver.HP3PARISCSIDriver,
"_iscsi_discover_target_iqn",
self.fake_iscsi_discover_target_iqn)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_get_3par_host",
self.fake_get_3par_host)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_delete_3par_host",
self.fake_delete_3par_host)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_3par_vlun",
self.fake_create_3par_vlun)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
self.driver = hpdriver.HP3PARISCSIDriver(configuration=configuration)
self.driver.do_setup(None)
target_iqn = 'iqn.2000-05.com.3pardata:21810002ac00383d'
#target_iqn = 'iqn.2000-05.com.3pardata:21810002ac00383d'
self.properties = {'data':
{'target_discovered': True,
'target_iqn': target_iqn,
'target_iqn': self.TARGET_IQN,
'target_lun': 186,
'target_portal': '1.1.1.2:1234'},
'driver_volume_type': 'iscsi'}
@@ -709,8 +698,17 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
self._hosts = {}
super(TestHP3PARISCSIDriver, self).tearDown()
def fake_iscsi_discover_target_iqn(self, ip_address):
return self.TARGET_IQN
def setup_driver(self, configuration, set_up_fakes=True):
self.driver = hpdriver.HP3PARISCSIDriver(configuration=configuration)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client",
self.fake_create_client)
if set_up_fakes:
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_ports",
self.fake_get_ports)
self.driver.do_setup(None)
def fake_create_3par_iscsi_host(self, hostname, iscsi_iqn,
domain, persona_id):
@@ -812,7 +810,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -836,7 +834,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -863,7 +861,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain",
self.fake_get_domain)
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
@@ -881,27 +879,11 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
host = self.driver._create_host(self.volume, self.connector)
self.assertEqual(host['name'], self.FAKE_HOST)
def test_iscsi_discover_target_iqn(self):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_port_cmd = 'showport -ids'
_run_ssh(show_port_cmd, False).AndReturn([pack(ISCSI_PORT_IDS_RET),
''])
self.mox.ReplayAll()
iqn = self.driver._iscsi_discover_target_iqn('10.10.120.253')
self.assertEqual(iqn, self.TARGET_IQN)
def test_get_volume_state(self):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
@@ -917,7 +899,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
self.flags(lock_path=self.tempdir)
#record
self.stubs.UnsetAll()
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
@@ -925,11 +907,144 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
_run_ssh(show_port_cmd, False).AndReturn([pack(PORT_RET), ''])
show_port_i_cmd = 'showport -iscsi'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(ISCSI_PORT_RET), ''])
_run_ssh(show_port_i_cmd, False).AndReturn([pack(READY_ISCSI_PORT_RET),
''])
show_port_i_cmd = 'showport -iscsiname'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI),
''])
self.mox.ReplayAll()
ports = self.driver.common.get_ports()
self.assertEqual(ports['FC'][0], '20210002AC00383D')
self.assertEqual(ports['iSCSI']['10.10.120.252']['nsp'], '0:8:2')
def test_get_iscsi_ip_active(self):
self.flags(lock_path=self.tempdir)
#record set up
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_port_cmd = 'showport'
_run_ssh(show_port_cmd, False).AndReturn([pack(PORT_RET), ''])
show_port_i_cmd = 'showport -iscsi'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(READY_ISCSI_PORT_RET),
''])
show_port_i_cmd = 'showport -iscsiname'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), ''])
self.mox.ReplayAll()
config = self.setup_configuration()
config.hp3par_iscsi_ips = ['10.10.220.253', '10.10.220.252']
self.setup_driver(config, set_up_fakes=False)
#record
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_vlun_cmd = 'showvlun -a -host fakehost'
_run_ssh(show_vlun_cmd, False).AndReturn([pack(SHOW_VLUN), ''])
self.mox.ReplayAll()
ip = self.driver._get_iscsi_ip('fakehost')
self.assertEqual(ip, '10.10.220.253')
def test_get_iscsi_ip(self):
self.flags(lock_path=self.tempdir)
#record driver set up
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_port_cmd = 'showport'
_run_ssh(show_port_cmd, False).AndReturn([pack(PORT_RET), ''])
show_port_i_cmd = 'showport -iscsi'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(READY_ISCSI_PORT_RET),
''])
show_port_i_cmd = 'showport -iscsiname'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), ''])
#record
show_vlun_cmd = 'showvlun -a -host fakehost'
show_vlun_ret = 'no vluns listed\r\n'
_run_ssh(show_vlun_cmd, False).AndReturn([pack(show_vlun_ret), ''])
show_vlun_cmd = 'showvlun -a -showcols Port'
_run_ssh(show_vlun_cmd, False).AndReturn([pack(SHOW_VLUN_NONE), ''])
self.mox.ReplayAll()
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)
ip = self.driver._get_iscsi_ip('fakehost')
self.assertEqual(ip, '10.10.220.252')
def test_invalid_iscsi_ip(self):
self.flags(lock_path=self.tempdir)
#record driver set up
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_port_cmd = 'showport'
_run_ssh(show_port_cmd, False).AndReturn([pack(PORT_RET), ''])
show_port_i_cmd = 'showport -iscsi'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(READY_ISCSI_PORT_RET),
''])
show_port_i_cmd = 'showport -iscsiname'
_run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), ''])
config = self.setup_configuration()
config.hp3par_iscsi_ips = ['10.10.220.250', '10.10.220.251']
config.iscsi_ip_address = '10.10.10.10'
self.mox.ReplayAll()
# no valid ip addr should be configured.
self.assertRaises(exception.InvalidInput,
self.setup_driver,
config,
set_up_fakes=False)
def test_get_least_used_nsp(self):
self.flags(lock_path=self.tempdir)
#record
self.clear_mox()
_run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh)
self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh)
show_vlun_cmd = 'showvlun -a -showcols Port'
_run_ssh(show_vlun_cmd, False).AndReturn([pack(SHOW_VLUN_NONE), ''])
_run_ssh(show_vlun_cmd, False).AndReturn([pack(SHOW_VLUN_NONE), ''])
_run_ssh(show_vlun_cmd, False).AndReturn([pack(SHOW_VLUN_NONE), ''])
self.mox.ReplayAll()
# in use count 11 12
nsp = self.driver._get_least_used_nsp(['0:2:1', '1:8:1'])
self.assertEqual(nsp, '0:2:1')
# in use count 11 10
nsp = self.driver._get_least_used_nsp(['0:2:1', '1:2:1'])
self.assertEqual(nsp, '1:2:1')
# in use count 0 10
nsp = self.driver._get_least_used_nsp(['1:1:1', '1:2:1'])
self.assertEqual(nsp, '1:1:1')
def pack(arg):
@@ -1106,3 +1221,40 @@ ISCSI_3PAR_RET = (
'Model : --\r\n'
'Contact : --\r\n'
'Comment : -- \r\n\r\n\r\n')
SHOW_PORT_ISCSI = (
'N:S:P,IPAddr,---------------iSCSI_Name----------------\r\n'
'0:8:1,1.1.1.2,iqn.2000-05.com.3pardata:21810002ac00383d\r\n'
'0:8:2,10.10.120.252,iqn.2000-05.com.3pardata:20820002ac00383d\r\n'
'1:8:1,10.10.220.253,iqn.2000-05.com.3pardata:21810002ac00383d\r\n'
'1:8:2,10.10.220.252,iqn.2000-05.com.3pardata:21820002ac00383d\r\n'
'-------------------------------------------------------------\r\n')
SHOW_VLUN = (
'Lun,VVName,HostName,---------Host_WWN/iSCSI_Name----------,Port,Type,'
'Status,ID\r\n'
'0,a,fakehost,iqn.1993-08.org.debian:01:3a779e4abc22,1:8:1,matched set,'
'active,0\r\n'
'------------------------------------------------------------------------'
'--------------\r\n')
SHOW_VLUN_NONE = (
'Port\r\n0:2:1\r\n0:2:1\r\n1:8:1\r\n1:8:1\r\n1:8:1\r\n1:2:1\r\n'
'1:2:1\r\n1:2:1\r\n1:2:1\r\n1:2:1\r\n1:2:1\r\n1:8:1\r\n1:8:1\r\n1:8:1\r\n'
'1:8:1\r\n1:8:1\r\n1:8:1\r\n0:2:1\r\n0:2:1\r\n0:2:1\r\n0:2:1\r\n0:2:1\r\n'
'0:2:1\r\n0:2:1\r\n1:8:1\r\n1:8:1\r\n0:2:1\r\n0:2:1\r\n1:2:1\r\n1:2:1\r\n'
'1:2:1\r\n1:2:1\r\n1:8:1\r\n-----')
READY_ISCSI_PORT_RET = (
'N:S:P,State,IPAddr,Netmask,Gateway,TPGT,MTU,Rate,DHCP,iSNS_Addr,'
'iSNS_Port\r\n'
'0:8:1,ready,10.10.120.253,255.255.224.0,0.0.0.0,81,1500,10Gbps,'
'0,0.0.0.0,3205\r\n'
'0:8:2,ready,10.10.120.252,255.255.224.0,0.0.0.0,82,1500,10Gbps,0,'
'0.0.0.0,3205\r\n'
'1:8:1,ready,10.10.220.253,255.255.224.0,0.0.0.0,181,1500,10Gbps,'
'0,0.0.0.0,3205\r\n'
'1:8:2,ready,10.10.220.252,255.255.224.0,0.0.0.0,182,1500,10Gbps,0,'
'0.0.0.0,3205\r\n'
'-------------------------------------------------------------------'
'----------------------\r\n')

View File

@@ -96,7 +96,10 @@ hp3par_opts = [
" and is deleted. This must be larger than expiration"),
cfg.BoolOpt('hp3par_debug',
default=False,
help="Enable HTTP debugging to 3PAR")
help="Enable HTTP debugging to 3PAR"),
cfg.ListOpt('hp3par_iscsi_ips',
default=[],
help="List of target iSCSI addresses to use.")
]
@@ -458,7 +461,7 @@ exit
# Protocol,Label,Partner,FailoverState
out = out[1:len(out) - 2]
ports = {'FC': [], 'iSCSI': []}
ports = {'FC': [], 'iSCSI': {}}
for line in out:
tmp = line.split(',')
@@ -477,9 +480,26 @@ exit
for line in out:
tmp = line.split(',')
if tmp:
if tmp and len(tmp) > 2:
if tmp[1] == 'ready':
ports['iSCSI'].append(tmp[2])
ports['iSCSI'][tmp[2]] = {}
# now get the nsp and iqn
result = self._cli_run('showport -iscsiname', None)
if result:
# first line is header
# nsp, ip,iqn
result = result[1:]
for line in result:
info = line.split(",")
if info and len(info) > 2:
if info[1] in ports['iSCSI']:
nsp = info[0]
ip_addr = info[1]
iqn = info[2]
ports['iSCSI'][ip_addr] = {'nsp': nsp,
'iqn': iqn
}
LOG.debug("PORTS = %s" % pprint.pformat(ports))
return ports

View File

@@ -30,6 +30,8 @@ Set the following in the cinder.conf file to enable the
volume_driver=cinder.volume.drivers.san.hp.hp_3par_iscsi.HP3PARISCSIDriver
"""
import sys
from hp3parclient import exceptions as hpexceptions
from cinder import exception
@@ -41,6 +43,7 @@ from cinder.volume.drivers.san import san
VERSION = 1.0
LOG = logging.getLogger(__name__)
DEFAULT_ISCSI_PORT = 3260
class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
@@ -62,8 +65,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
def _check_flags(self):
"""Sanity check to ensure we have required options set."""
required_flags = ['hp3par_api_url', 'hp3par_username',
'hp3par_password', 'iscsi_ip_address',
'iscsi_port', 'san_ip', 'san_login',
'hp3par_password', 'san_ip', 'san_login',
'san_password']
self.common.check_flags(self.configuration, required_flags)
@@ -80,10 +82,66 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
def do_setup(self, context):
self.common = self._init_common()
self._check_flags()
self.common.do_setup(context)
# make sure ssh works.
self._iscsi_discover_target_iqn(self.configuration.iscsi_ip_address)
# map iscsi_ip-> ip_port
# -> iqn
# -> nsp
self.iscsi_ips = {}
temp_iscsi_ip = {}
# use the 3PAR ip_addr list for iSCSI configuration
if len(self.configuration.hp3par_iscsi_ips) > 0:
# add port values to ip_addr, if necessary
for ip_addr in self.configuration.hp3par_iscsi_ips:
ip = ip_addr.split(':')
if len(ip) == 1:
temp_iscsi_ip[ip_addr] = {'ip_port': DEFAULT_ISCSI_PORT}
elif len(ip) == 2:
temp_iscsi_ip[ip[0]] = {'ip_port': ip[1]}
else:
msg = _("Invalid IP address format '%s'") % ip_addr
LOG.warn(msg)
# add the single value iscsi_ip_address option to the IP dictionary.
# This way we can see if it's a valid iSCSI IP. If it's not valid,
# we won't use it and won't bother to report it, see below
if (self.configuration.iscsi_ip_address not in temp_iscsi_ip):
ip = self.configuration.iscsi_ip_address
ip_port = self.configuration.iscsi_port
temp_iscsi_ip[ip] = {'ip_port': ip_port}
# get all the valid iSCSI ports from 3PAR
# when found, add the valid iSCSI ip, ip port, iqn and nsp
# to the iSCSI IP dictionary
# ...this will also make sure ssh works.
iscsi_ports = self.common.get_ports()['iSCSI']
for (ip, iscsi_info) in iscsi_ports.iteritems():
if ip in temp_iscsi_ip:
ip_port = temp_iscsi_ip[ip]['ip_port']
self.iscsi_ips[ip] = {'ip_port': ip_port,
'nsp': iscsi_info['nsp'],
'iqn': iscsi_info['iqn']
}
del temp_iscsi_ip[ip]
# if the single value iscsi_ip_address option is still in the
# temp dictionary it's because it defaults to $my_ip which doesn't
# make sense in this context. So, if present, remove it and move on.
if (self.configuration.iscsi_ip_address in temp_iscsi_ip):
del temp_iscsi_ip[self.configuration.iscsi_ip_address]
# lets see if there are invalid iSCSI IPs left in the temp dict
if len(temp_iscsi_ip) > 0:
msg = _("Found invalid iSCSI IP address(s) in configuration "
"option(s) hp3par_iscsi_ips or iscsi_ip_address '%s.'") % \
(", ".join(temp_iscsi_ip))
LOG.warn(msg)
if not len(self.iscsi_ips) > 0:
msg = _('At least one valid iSCSI IP address must be set.')
raise exception.InvalidInput(reason=(msg))
self.common.do_setup(context)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
@@ -95,10 +153,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
metadata = self.common.create_volume(volume)
self.common.client_logout()
return {'provider_location': "%s:%s" %
(self.configuration.iscsi_ip_address,
self.configuration.iscsi_port),
'metadata': metadata}
return {'metadata': metadata}
@utils.synchronized('3par', external=True)
def create_cloned_volume(self, volume, src_vref):
@@ -107,10 +162,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
new_vol = self.common.create_cloned_volume(volume, src_vref)
self.common.client_logout()
return {'provider_location': "%s:%s" %
(self.configuration.iscsi_ip_address,
self.configuration.iscsi_port),
'metadata': new_vol}
return {'metadata': new_vol}
@utils.synchronized('3par', external=True)
def delete_volume(self, volume):
@@ -168,9 +220,6 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
* create vlun on the 3par
"""
self.common.client_login()
# get the target_iqn on the 3par interface.
target_iqn = self._iscsi_discover_target_iqn(
self.configuration.iscsi_ip_address)
# we have to make sure we have a host
host = self._create_host(volume, connector)
@@ -179,11 +228,14 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
vlun = self.common.create_vlun(volume, host)
self.common.client_logout()
iscsi_ip = self._get_iscsi_ip(host['name'])
iscsi_ip_port = self.iscsi_ips[iscsi_ip]['ip_port']
iscsi_target_iqn = self.iscsi_ips[iscsi_ip]['iqn']
info = {'driver_volume_type': 'iscsi',
'data': {'target_portal': "%s:%s" %
(self.configuration.iscsi_ip_address,
self.configuration.iscsi_port),
'target_iqn': target_iqn,
(iscsi_ip, iscsi_ip_port),
'target_iqn': iscsi_target_iqn,
'target_lun': vlun['lun'],
'target_discovered': True
}
@@ -199,21 +251,6 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
connector['initiator'])
self.common.client_logout()
def _iscsi_discover_target_iqn(self, remote_ip):
result = self.common._cli_run('showport -ids', None)
iqn = None
if result:
# first line is header
result = result[1:]
for line in result:
info = line.split(",")
if info and len(info) > 2:
if info[1] == remote_ip:
iqn = info[2]
return iqn
def _create_3par_iscsi_host(self, hostname, iscsi_iqn, domain, persona_id):
"""Create a 3PAR host.
@@ -268,3 +305,81 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
@utils.synchronized('3par', external=True)
def remove_export(self, context, volume):
pass
def _get_iscsi_ip(self, hostname):
"""Get an iSCSI IP address to use.
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
"""
if len(self.iscsi_ips) == 1:
return self.iscsi_ips.keys()[0]
# if we currently have an active port, use it
nsp = self._get_active_nsp(hostname)
if nsp is None:
# no active vlun, find least busy port
nsp = self._get_least_used_nsp(self._get_iscsi_nsps())
if 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(nsp)
def _get_iscsi_nsps(self):
"""Return the list of candidate nsps."""
nsps = []
for value in self.iscsi_ips.values():
nsps.append(value['nsp'])
return nsps
def _get_ip_using_nsp(self, nsp):
"""Return IP assiciated with given nsp."""
for (key, value) in self.iscsi_ips.items():
if value['nsp'] == nsp:
return key
def _get_active_nsp(self, hostname):
"""Return the active nsp, if one exists, for the given host."""
result = self.common._cli_run('showvlun -a -host %s' % hostname, None)
if result:
# first line is header
result = result[1:]
for line in result:
info = line.split(",")
if info and len(info) > 4:
return info[4]
def _get_least_used_nsp(self, nspss):
""""Return the nsp that has the fewest active vluns."""
# return only the nsp (node:server:port)
result = self.common._cli_run('showvlun -a -showcols Port', None)
# count the number of nsps (there is 1 for each active vlun)
nsp_counts = {}
for nsp in nspss:
# initialize counts to zero
nsp_counts[nsp] = 0
current_least_used_nsp = None
if result:
# first line is header
result = result[1:]
for line in result:
nsp = line.strip()
if nsp in nsp_counts:
nsp_counts[nsp] = nsp_counts[nsp] + 1
# identify key (nsp) of least used nsp
current_smallest_count = sys.maxint
for (nsp, count) in nsp_counts.iteritems():
if count < current_smallest_count:
current_least_used_nsp = nsp
current_smallest_count = count
return current_least_used_nsp

View File

@@ -1160,6 +1160,9 @@
# Enable HTTP debugging to 3PAR (boolean value)
#hp3par_debug=false
#List of target iSCSI addresses to use (list value)
#hp3par_iscsi_ips=
#
# Options defined in cinder.volume.drivers.san.san