From f0ce71c23c786baf7c828d1fd147d70342593a10 Mon Sep 17 00:00:00 2001 From: Jim Branen Date: Mon, 19 Aug 2013 16:36:16 -0700 Subject: [PATCH] Modified 3PAR drives to support 3parclient 2.0.0 Replaced ssh calls to remove host, show host, show port, show vlun and create vlun with the new hp3parclient calls. Change-Id: I917b4263389331597103d2fe6c9a73994165051b Fixes: bug #1211994 --- cinder/tests/test_hp3par.py | 573 ++++++++---------- .../volume/drivers/san/hp/hp_3par_common.py | 255 +++----- cinder/volume/drivers/san/hp/hp_3par_fc.py | 22 +- cinder/volume/drivers/san/hp/hp_3par_iscsi.py | 98 ++- test-requirements.txt | 2 +- 5 files changed, 406 insertions(+), 544 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 6778a326b..011c47172 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -42,6 +42,27 @@ CLI_CR = '\r\n' class FakeHP3ParClient(object): + PORT_MODE_TARGET = 2 + PORT_MODE_INITIATOR = 3 + PORT_MODE_PEER = 4 + + PORT_TYPE_HOST = 1 + PORT_TYPE_DISK = 2 + PORT_TYPE_FREE = 3 + PORT_TYPE_RCIP = 6 + PORT_TYPE_ISCSI = 7 + + PORT_PROTO_FC = 1 + PORT_PROTO_ISCSI = 2 + PORT_PROTO_IP = 4 + + PORT_STATE_READY = 4 + PORT_STATE_SYNC = 5 + PORT_STATE_OFFLINE = 10 + + HOST_EDIT_ADD = 1 + HOST_EDIT_REMOVE = 2 + api_url = None debug = False @@ -290,6 +311,15 @@ class FakeHP3ParClient(object): 'desc': "VLUN '%s' was not found" % volumeName} raise hpexceptions.HTTPNotFound(msg) + def getHost(self, hostname): + return None + + def modifyHost(self, hostname, options): + return None + + def getPorts(self): + return None + class HP3PARBaseDriver(): @@ -305,14 +335,25 @@ class HP3PARBaseDriver(): PROJECT_ID = 'fac88235b9d64685a3530f73e490348f' VOLUME_ID_SNAP = '761fc5e5-5191-4ec7-aeba-33e36de44156' FAKE_DESC = 'test description name' - FAKE_FC_PORTS = ['0987654321234', '123456789000987'] + FAKE_FC_PORTS = [{'portPos': {'node': 7, 'slot': 1, 'cardPort': 1}, + 'portWWN': '0987654321234', + 'protocol': 1, + 'mode': 2, + 'linkState': 4}, + {'portPos': {'node': 6, 'slot': 1, 'cardPort': 1}, + 'portWWN': '123456789000987', + 'protocol': 1, + 'mode': 2, + 'linkState': 4}] QOS = {'qos:maxIOPS': '1000', 'qos:maxBWS': '50'} VVS_NAME = "myvvs" - FAKE_ISCSI_PORTS = {'1.1.1.2': {'nsp': '8:1:1', - 'iqn': ('iqn.2000-05.com.3pardata:' - '21810002ac00383d'), - 'ip_port': '3262'}} - + FAKE_ISCSI_PORT = {'portPos': {'node': 8, 'slot': 1, 'cardPort': 1}, + 'protocol': 2, + 'mode': 2, + 'IPAddr': '1.1.1.2', + 'iSCSIName': ('iqn.2000-05.com.3pardata:' + '21810002ac00383d'), + 'linkState': 4} volume = {'name': VOLUME_NAME, 'id': VOLUME_ID, 'display_name': 'Foo Volume', @@ -440,7 +481,9 @@ class HP3PARBaseDriver(): self.driver.common.client.createVLUN(volume, 19, hostname) def fake_get_ports(self): - return {'FC': self.FAKE_FC_PORTS, 'iSCSI': self.FAKE_ISCSI_PORTS} + ports = self.FAKE_FC_PORTS + ports.append(self.FAKE_ISCSI_PORT) + return {'members': ports} def fake_get_volume_type(self, type_id): return self.volume_type @@ -725,15 +768,18 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack('no hosts listed'), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) + + ex = hpexceptions.HTTPNotFound('Host not found.') + getHost('fakehost').AndRaise(ex) create_host_cmd = (['createhost', '-persona', '1', '-domain', ('OpenStack',), 'fakehost', '123456789012345', '123456789054321']) _run_ssh(create_host_cmd, False).AndReturn([CLI_CR, '']) - _run_ssh(show_host_cmd, False).AndReturn([pack(FC_HOST_RET), '']) + getHost('fakehost').AndReturn({'name': self.FAKE_HOST}) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -751,8 +797,11 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack('no hosts listed'), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) + + not_found_ex = hpexceptions.HTTPNotFound('Host not found.') + getHost('fakehost').AndRaise(not_found_ex) create_host_cmd = (['createhost', '-persona', '1', '-domain', ('OpenStack',), 'fakehost', '123456789012345', @@ -761,8 +810,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'already used by host fakehost.foo (19)') _run_ssh(create_host_cmd, False).AndReturn([create_host_ret, '']) - show_3par_cmd = ['showhost', '-verbose', 'fakehost.foo'] - _run_ssh(show_3par_cmd, False).AndReturn([pack(FC_SHOWHOST_RET), '']) + getHost('fakehost.foo').AndReturn({'name': 'fakehost.foo'}) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -778,22 +826,30 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): self.fake_get_cpg) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain", self.fake_get_domain) - _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack(NO_FC_HOST_RET), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) - create_host_cmd = ['createhost', '-add', 'fakehost', '123456789012345', - '123456789054321'] - _run_ssh(create_host_cmd, False).AndReturn([CLI_CR, '']) + modifyHost = self.mox.CreateMock(FakeHP3ParClient.modifyHost) + self.stubs.Set(FakeHP3ParClient, "modifyHost", modifyHost) + + getHost('fakehost').AndReturn(({'name': self.FAKE_HOST, + 'FCPaths': []})) + + modifyHost('fakehost', {'FCWWNs': + ['123456789012345', '123456789054321'], + 'pathOperation': 1}) + + getHost('fakehost').AndReturn({'name': self.FAKE_HOST, + 'FCPaths': [{'WWN': '123456789012345'}, + {'WWN': '123456789054321'}]} + ) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack(FC_HOST_RET), '']) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) self.assertEqual(host['name'], self.FAKE_HOST) + self.assertEqual(len(host['FCPaths']), 2) class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): @@ -920,15 +976,18 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack('no hosts listed'), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) + + not_found_ex = hpexceptions.HTTPNotFound('Host not found.') + getHost('fakehost').AndRaise(not_found_ex) create_host_cmd = (['createhost', '-iscsi', '-persona', '1', '-domain', ('OpenStack',), 'fakehost', 'iqn.1993-08.org.debian:01:222']) _run_ssh(create_host_cmd, False).AndReturn([CLI_CR, '']) - _run_ssh(show_host_cmd, False).AndReturn([pack(ISCSI_HOST_RET), '']) + getHost('fakehost').AndReturn({'name': self.FAKE_HOST}) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -946,8 +1005,11 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack('no hosts listed'), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) + + not_found_ex = hpexceptions.HTTPNotFound('Host not found.') + getHost('fakehost').AndRaise(not_found_ex) create_host_cmd = (['createhost', '-iscsi', '-persona', '1', '-domain', ('OpenStack',), 'fakehost', @@ -955,8 +1017,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): in_use_ret = pack('\r\nalready used by host fakehost.foo ') _run_ssh(create_host_cmd, False).AndReturn([in_use_ret, '']) - show_3par_cmd = ['showhost', '-verbose', 'fakehost.foo'] - _run_ssh(show_3par_cmd, False).AndReturn([pack(ISCSI_3PAR_RET), '']) + getHost('fakehost.foo').AndReturn({'name': 'fakehost.foo'}) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) @@ -972,77 +1033,63 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): self.fake_get_cpg) self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain", self.fake_get_domain) - _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) - show_host_cmd = ['showhost', '-verbose', 'fakehost'] - _run_ssh(show_host_cmd, False).AndReturn([pack(ISCSI_NO_HOST_RET), '']) + getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) + self.stubs.Set(FakeHP3ParClient, "getHost", getHost) - create_host_cmd = ['createhost', '-iscsi', '-add', 'fakehost', - 'iqn.1993-08.org.debian:01:222'] - _run_ssh(create_host_cmd, False).AndReturn([CLI_CR, '']) - _run_ssh(show_host_cmd, False).AndReturn([pack(ISCSI_HOST_RET), '']) + modifyHost = self.mox.CreateMock(FakeHP3ParClient.modifyHost) + self.stubs.Set(FakeHP3ParClient, "modifyHost", modifyHost) + + getHost('fakehost').AndReturn(({'name': self.FAKE_HOST, + 'iSCSIPaths': []})) + + modifyHost('fakehost', {'iSCSINames': + ['iqn.1993-08.org.debian:01:222'], + 'pathOperation': 1}) + + ret_value = {'name': self.FAKE_HOST, + 'iSCSIPaths': [{'name': 'iqn.1993-08.org.debian:01:222'}] + } + getHost('fakehost').AndReturn(ret_value) self.mox.ReplayAll() host = self.driver._create_host(self.volume, self.connector) self.assertEqual(host['name'], self.FAKE_HOST) + self.assertEqual(len(host['iSCSIPaths']), 1) def test_get_ports(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_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), - '']) + getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts) + self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts) - show_port_i_cmd = ['showport', '-iscsiname'] - _run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), - '']) + getPorts().AndReturn(PORTS1_RET) 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') + ports = self.driver.common.get_ports()['members'] + self.assertTrue(len(ports) == 3) 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), - '']) + getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts) + self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts) - show_port_i_cmd = ['showport', '-iscsiname'] - _run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), '']) + getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs) + self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs) + getPorts().AndReturn(PORTS_RET) + getVLUNs().AndReturn(VLUNS2_RET) 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') @@ -1053,26 +1100,14 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): #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), - '']) + getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts) + self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts) - 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), '']) + getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs) + self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs) + getPorts().AndReturn(PORTS_RET) + getVLUNs().AndReturn(VLUNS1_RET) self.mox.ReplayAll() config = self.setup_configuration() @@ -1088,18 +1123,10 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): #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), - '']) + getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts) + self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts) - show_port_i_cmd = ['showport', '-iscsiname'] - _run_ssh(show_port_i_cmd, False).AndReturn([pack(SHOW_PORT_ISCSI), '']) + getPorts().AndReturn(PORTS_RET) config = self.setup_configuration() config.hp3par_iscsi_ips = ['10.10.220.250', '10.10.220.251'] @@ -1117,25 +1144,30 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): #record self.clear_mox() - _run_ssh = self.mox.CreateMock(hpdriver.hpcommon.HP3PARCommon._run_ssh) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_run_ssh", _run_ssh) + getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs) + self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs) - 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), '']) + getVLUNs().AndReturn(VLUNS3_RET) + getVLUNs().AndReturn(VLUNS4_RET) + getVLUNs().AndReturn(VLUNS4_RET) 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']) + # in use count + vluns = self.driver.common.client.getVLUNs() + nsp = self.driver._get_least_used_nsp(vluns['members'], + ['0:2:1', '1:8:1']) + self.assertEqual(nsp, '1:8:1') + + # in use count + vluns = self.driver.common.client.getVLUNs() + nsp = self.driver._get_least_used_nsp(vluns['members'], + ['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']) + # in use count + vluns = self.driver.common.client.getVLUNs() + nsp = self.driver._get_least_used_nsp(vluns['members'], + ['1:1:1', '1:2:1']) self.assertEqual(nsp, '1:1:1') @@ -1144,209 +1176,108 @@ def pack(arg): footer = '\r\n\r\n\r\n' return header + arg + footer -FC_HOST_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '75,fakehost,Generic,50014380242B8B4C,0:2:1,n/a\r\n' - '75,fakehost,Generic,50014380242B8B4E,---,n/a\r\n' - '75,fakehost,Generic,1000843497F90711,0:2:1,n/a \r\n' - '75,fakehost,Generic,1000843497F90715,1:2:1,n/a\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost,--,--\r\n' - '\r\n' - '---------- Host fakehost ----------\r\n' - 'Name : fakehost\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - 'Model : --\r\n' - 'Contact : --\r\n' - 'Comment : -- \r\n\r\n\r\n') - -FC_SHOWHOST_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '75,fakehost.foo,Generic,50014380242B8B4C,0:2:1,n/a\r\n' - '75,fakehost.foo,Generic,50014380242B8B4E,---,n/a\r\n' - '75,fakehost.foo,Generic,1000843497F90711,0:2:1,n/a \r\n' - '75,fakehost.foo,Generic,1000843497F90715,1:2:1,n/a\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost.foo,--,--\r\n' - '\r\n' - '---------- Host fakehost.foo ----------\r\n' - 'Name : fakehost.foo\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - 'Model : --\r\n' - 'Contact : --\r\n' - 'Comment : -- \r\n\r\n\r\n') - -NO_FC_HOST_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost,--,--\r\n' - '\r\n' - '---------- Host fakehost ----------\r\n' - 'Name : fakehost\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - 'Model : --\r\n' - 'Contact : --\r\n' - 'Comment : -- \r\n\r\n\r\n') - -ISCSI_HOST_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '75,fakehost,Generic,iqn.1993-08.org.debian:01:222,---,10.10.222.12\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost,--,--\r\n' - '\r\n' - '---------- Host fakehost ----------\r\n' - 'Name : fakehost\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - 'Model : --\r\n' - 'Contact : --\r\n' - 'Comment : -- \r\n\r\n\r\n') - -ISCSI_NO_HOST_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost,--,--\r\n' - '\r\n' - '---------- Host fakehost ----------\r\n' - 'Name : fakehost\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - 'Model : --\r\n' - 'Contact : --\r\n' - 'Comment : -- \r\n\r\n\r\n') - -ISCSI_PORT_IDS_RET = ( - 'N:S:P,-Node_WWN/IPAddr-,-----------Port_WWN/iSCSI_Name-----------\r\n' - '0:2:1,28210002AC00383D,20210002AC00383D\r\n' - '0:2:2,2FF70002AC00383D,20220002AC00383D\r\n' - '0:2:3,2FF70002AC00383D,20230002AC00383D\r\n' - '0:2:4,2FF70002AC00383D,20240002AC00383D\r\n' - '0:5:1,2FF70002AC00383D,20510002AC00383D\r\n' - '0:5:2,2FF70002AC00383D,20520002AC00383D\r\n' - '0:5:3,2FF70002AC00383D,20530002AC00383D\r\n' - '0:5:4,2FF70202AC00383D,20540202AC00383D\r\n' - '0:6:4,2FF70002AC00383D,20640002AC00383D\r\n' - '0:8:1,10.10.120.253,iqn.2000-05.com.3pardata:21810002ac00383d\r\n' - '0:8:2,0.0.0.0,iqn.2000-05.com.3pardata:20820002ac00383d\r\n' - '1:2:1,29210002AC00383D,21210002AC00383D\r\n' - '1:2:2,2FF70002AC00383D,21220002AC00383D\r\n' - '-----------------------------------------------------------------\r\n') - -VOLUME_STATE_RET = ( - 'Id,Name,Prov,Type,State,-Detailed_State-\r\n' - '410,volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7,snp,vcopy,normal,' - 'normal\r\n' - '-----------------------------------------------------------------\r\n') - -PORT_RET = ( - 'N:S:P,Mode,State,----Node_WWN----,-Port_WWN/HW_Addr-,Type,Protocol,' - 'Label,Partner,FailoverState\r\n' - '0:2:1,target,ready,28210002AC00383D,20210002AC00383D,host,FC,' - '-,1:2:1,none\r\n' - '0:2:2,initiator,loss_sync,2FF70002AC00383D,20220002AC00383D,free,FC,' - '-,-,-\r\n' - '0:2:3,initiator,loss_sync,2FF70002AC00383D,20230002AC00383D,free,FC,' - '-,-,-\r\n' - '0:2:4,initiator,loss_sync,2FF70002AC00383D,20240002AC00383D,free,FC,' - '-,-,-\r\n' - '0:5:1,initiator,loss_sync,2FF70002AC00383D,20510002AC00383D,free,FC,' - '-,-,-\r\n' - '0:5:2,initiator,loss_sync,2FF70002AC00383D,20520002AC00383D,free,FC,' - '-,-,-\r\n' - '0:5:3,initiator,loss_sync,2FF70002AC00383D,20530002AC00383D,free,FC,' - '-,-,-\r\n' - '0:5:4,initiator,ready,2FF70202AC00383D,20540202AC00383D,host,FC,' - '-,1:5:4,active\r\n' - '0:6:1,initiator,ready,2FF70002AC00383D,20610002AC00383D,disk,FC,' - '-,-,-\r\n' - '0:6:2,initiator,ready,2FF70002AC00383D,20620002AC00383D,disk,FC,' - '-,-,-\r\n') - -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,loss_sync,0.0.0.0,0.0.0.0,0.0.0.0,82,1500,n/a,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,loss_sync,0.0.0.0,0.0.0.0,0.0.0.0,182,1500,n/a,0,0.0.0.0,3205\r\n') - -ISCSI_3PAR_RET = ( - 'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n' - '75,fakehost.foo,Generic,iqn.1993-08.org.debian:01:222,---,' - '10.10.222.12\r\n' - '\r\n' - 'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n' - '75,fakehost.foo,--,--\r\n' - '\r\n' - '---------- Host fakehost.foo ----------\r\n' - 'Name : fakehost.foo\r\n' - 'Domain : FAKE_TEST\r\n' - 'Id : 75\r\n' - 'Location : --\r\n' - 'IP Address : --\r\n' - 'OS : --\r\n' - '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') +PORTS_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'protocol': 2, + 'IPAddr': '10.10.220.252', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21820002ac00383d', + 'mode': 2, + 'HWAddr': '2C27D75375D2', + 'type': 8}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'protocol': 2, + 'IPAddr': '10.10.220.253', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21810002ac00383d', + 'mode': 2, + 'HWAddr': '2C27D75375D6', + 'type': 8}]}) + +PORTS1_RET = ({'members': + [{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2}, + 'protocol': 2, + 'IPAddr': '10.10.120.252', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21820002ac00383d', + 'mode': 2, + 'HWAddr': '2C27D75375D2', + 'type': 8}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'protocol': 2, + 'IPAddr': '10.10.220.253', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21810002ac00383d', + 'mode': 2, + 'HWAddr': '2C27D75375D6', + 'type': 8}, + {'portWWN': '20210002AC00383D', + 'protocol': 1, + 'linkState': 4, + 'mode': 2, + 'device': ['cage2'], + 'nodeWWN': '20210002AC00383D', + 'type': 2, + 'portPos': {'node': 0, 'slot': 6, 'cardPort': 3}}]}) + +VLUNS1_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'hostname': 'foo', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'hostname': 'bar', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'hostname': 'bar', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'hostname': 'bar', 'active': True}]}) + +VLUNS2_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'hostname': 'bar', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'hostname': 'fakehost', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'hostname': 'bar', 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'hostname': 'bar', 'active': True}]}) + +VLUNS3_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 2}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}]}) + +VLUNS4_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}, + {'portPos': {'node': 0, 'slot': 2, 'cardPort': 1}, + 'active': True}]}) diff --git a/cinder/volume/drivers/san/hp/hp_3par_common.py b/cinder/volume/drivers/san/hp/hp_3par_common.py index 2b4ba2418..e84aedd5d 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -48,6 +48,7 @@ import time import uuid from eventlet import greenthread +import hp3parclient from hp3parclient import client from hp3parclient import exceptions as hpexceptions from oslo.config import cfg @@ -64,6 +65,8 @@ from cinder.volume import volume_types LOG = logging.getLogger(__name__) +MIN_CLIENT_VERSION = '2.0.0' + hp3par_opts = [ cfg.StrOpt('hp3par_api_url', default='', @@ -144,7 +147,17 @@ class HP3PARCommon(object): raise exception.InvalidInput(reason=_('%s is not set') % flag) def _create_client(self): - return client.HP3ParClient(self.config.hp3par_api_url) + cl = client.HP3ParClient(self.config.hp3par_api_url) + client_version = hp3parclient.version + + if (client_version < MIN_CLIENT_VERSION): + ex_msg = (_('Invalid hp3parclient version. Version %s or greater ' + 'required.') % MIN_CLIENT_VERSION) + raise hpexceptions.UnsupportedVersion(ex_msg) + else: + LOG.debug(('Using hp3parclient %s.') % client_version) + + return cl def client_login(self): try: @@ -358,15 +371,14 @@ exit LOG.error(_("Error running ssh command: %s") % command) def _delete_3par_host(self, hostname): - self._cli_run(['removehost', hostname]) + self.client.deleteHost(hostname) def _create_3par_vlun(self, volume, hostname): - out = self._cli_run(['createvlun', volume, 'auto', hostname]) - if out and len(out) > 1: - if "must be in the same domain" in out[0]: - err = out[0].strip() - err = err + " " + out[1].strip() - raise exception.Invalid3PARDomain(err=err) + try: + self.client.createVLUN(volume, hostname=hostname, auto=True) + except hpexceptions.HTTPBadRequest as e: + if 'must be in the same domain' in e.get_description(): + raise exception.Invalid3PARDomain(err=e.get_description()) def _safe_hostname(self, hostname): """We have to use a safe hostname length for 3PAR host names.""" @@ -383,142 +395,41 @@ exit return hostname[:index] def _get_3par_host(self, hostname): - out = self._cli_run(['showhost', '-verbose', hostname]) - LOG.debug("OUTPUT = \n%s" % (pprint.pformat(out))) - host = {'id': None, 'name': None, - 'domain': None, - 'descriptors': {}, - 'iSCSIPaths': [], - 'FCPaths': []} - - if out: - err = out[0] - if err == 'no hosts listed': - msg = {'code': 'NON_EXISTENT_HOST', - 'desc': "HOST '%s' was not found" % hostname} - raise hpexceptions.HTTPNotFound(msg) - - # start parsing the lines after the header line - for line in out[1:]: - if line == '': - break - tmp = line.split(',') - paths = {} - - LOG.debug("line = %s" % (pprint.pformat(tmp))) - host['id'] = tmp[0] - host['name'] = tmp[1] - - portPos = tmp[4] - LOG.debug("portPos = %s" % (pprint.pformat(portPos))) - if portPos == '---': - portPos = None - else: - port = portPos.split(':') - portPos = {'node': int(port[0]), 'slot': int(port[1]), - 'cardPort': int(port[2])} - - paths['portPos'] = portPos - - # If FC entry - if tmp[5] == 'n/a': - paths['wwn'] = tmp[3] - host['FCPaths'].append(paths) - # else iSCSI entry - else: - paths['name'] = tmp[3] - paths['ipAddr'] = tmp[5] - host['iSCSIPaths'].append(paths) - - # find the offset to the description stuff - offset = 0 - for line in out: - if line[:15] == '---------- Host': - break - else: - offset += 1 - - info = out[offset + 2] - tmp = info.split(':') - host['domain'] = tmp[1] - - info = out[offset + 4] - tmp = info.split(':') - host['descriptors']['location'] = tmp[1] - - info = out[offset + 5] - tmp = info.split(':') - host['descriptors']['ipAddr'] = tmp[1] - - info = out[offset + 6] - tmp = info.split(':') - host['descriptors']['os'] = tmp[1] - - info = out[offset + 7] - tmp = info.split(':') - host['descriptors']['model'] = tmp[1] - - info = out[offset + 8] - tmp = info.split(':') - host['descriptors']['contact'] = tmp[1] - - info = out[offset + 9] - tmp = info.split(':') - host['descriptors']['comment'] = tmp[1] - - return host + return self.client.getHost(hostname) def get_ports(self): - # First get the active FC ports - out = self._cli_run(['showport']) - - # strip out header - # N:S:P,Mode,State,----Node_WWN----,-Port_WWN/HW_Addr-,Type, - # Protocol,Label,Partner,FailoverState - out = out[1:len(out) - 2] - - ports = {'FC': [], 'iSCSI': {}} - for line in out: - tmp = line.split(',') - - if tmp: - if tmp[1] == 'target' and tmp[2] == 'ready': - if tmp[6] == 'FC': - ports['FC'].append(tmp[4]) - - # now get the active iSCSI ports - out = self._cli_run(['showport', '-iscsi']) - - # strip out header - # N:S:P,State,IPAddr,Netmask,Gateway, - # TPGT,MTU,Rate,DHCP,iSNS_Addr,iSNS_Port - out = out[1:len(out) - 2] - for line in out: - tmp = line.split(',') - - if tmp and len(tmp) > 2: - if tmp[1] == 'ready': - ports['iSCSI'][tmp[2]] = {} - - # now get the nsp and iqn - result = self._cli_run(['showport', '-iscsiname']) - 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 + return self.client.getPorts() + + def get_active_target_ports(self): + ports = self.get_ports() + target_ports = [] + for port in ports['members']: + if ( + port['mode'] == self.client.PORT_MODE_TARGET and + port['linkState'] == self.client.PORT_STATE_READY + ): + port['nsp'] = self.build_nsp(port['portPos']) + target_ports.append(port) + + return target_ports + + def get_active_fc_target_ports(self): + ports = self.get_active_target_ports() + fc_ports = [] + for port in ports: + if port['protocol'] == self.client.PORT_PROTO_FC: + fc_ports.append(port) + + return fc_ports + + def get_active_iscsi_target_ports(self): + ports = self.get_active_target_ports() + iscsi_ports = [] + for port in ports: + if port['protocol'] == self.client.PORT_PROTO_ISCSI: + iscsi_ports.append(port) + + return iscsi_ports def get_volume_stats(self, refresh): if refresh: @@ -574,7 +485,15 @@ exit volume_name = self._get_3par_vol_name(volume['id']) vlun = self.client.getVLUN(volume_name) self.client.deleteVLUN(volume_name, vlun['lun'], hostname) - self._delete_3par_host(hostname) + try: + self._delete_3par_host(hostname) + except hpexceptions.HTTPConflict as ex: + # host will only be removed after all vluns + # have been removed + if 'has exported VLUN' in ex.get_description(): + pass + else: + raise def _get_volume_type(self, type_id): ctxt = context.get_admin_context() @@ -1027,21 +946,30 @@ exit except hpexceptions.HTTPNotFound as ex: LOG.error(str(ex)) - def _get_3par_hostname_from_wwn_iqn(self, wwns_iqn): - out = self._cli_run(['showhost', '-d']) - # wwns_iqn may be a list of strings or a single - # string. So, if necessary, create a list to loop. - if not isinstance(wwns_iqn, list): - wwn_iqn_list = [wwns_iqn] - else: - wwn_iqn_list = wwns_iqn - - for wwn_iqn in wwn_iqn_list: - for showhost in out: - if (wwn_iqn.upper() in showhost.upper()): - return showhost.split(',')[1] - - def terminate_connection(self, volume, hostname, wwn_iqn): + def _get_3par_hostname_from_wwn_iqn(self, wwns, iqns): + if wwns is not None and not isinstance(wwns, list): + wwns = [wwns] + if iqns is not None and not isinstance(iqns, list): + iqns = [iqns] + + out = self.client.getHosts() + hosts = out['members'] + for host in hosts: + if 'iSCSIPaths' in host and iqns is not None: + iscsi_paths = host['iSCSIPaths'] + for iscsi in iscsi_paths: + for iqn in iqns: + if iqn == iscsi['name']: + return host['name'] + + if 'FCPaths' in host and wwns is not None: + fc_paths = host['FCPaths'] + for fc in fc_paths: + for wwn in wwns: + if wwn == fc['WWN']: + return host['name'] + + def terminate_connection(self, volume, hostname, wwn=None, iqn=None): """Driver entry point to unattach a volume from an instance.""" try: # does 3par know this host by a different name? @@ -1052,7 +980,7 @@ exit except hpexceptions.HTTPNotFound as e: if 'host does not exist' in e.get_description(): # use the wwn to see if we can find the hostname - hostname = self._get_3par_hostname_from_wwn_iqn(wwn_iqn) + hostname = self._get_3par_hostname_from_wwn_iqn(wwn, iqn) # no 3par host, re-throw if (hostname is None): raise @@ -1060,13 +988,18 @@ exit # not a 'host does not exist' HTTPNotFound exception, re-throw raise - #try again with name retrieved from 3par + # try again with name retrieved from 3par self.delete_vlun(volume, hostname) def parse_create_host_error(self, hostname, out): search_str = "already used by host " if search_str in out[1]: - #host exists, return name used by 3par + # host exists, return name used by 3par hostname_3par = self.get_next_word(out[1], search_str) self.hosts_naming_dict[hostname] = hostname_3par return hostname_3par + + def build_nsp(self, portPos): + return '%s:%s:%s' % (portPos['node'], + portPos['slot'], + portPos['cardPort']) diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index 4be855718..8a1d2d249 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -180,13 +180,17 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): # now that we have a host, create the VLUN vlun = self.common.create_vlun(volume, host) - ports = self.common.get_ports() + fc_ports = self.common.get_active_fc_target_ports() + wwns = [] + + for port in fc_ports: + wwns.append(port['portWWN']) self.common.client_logout() info = {'driver_volume_type': 'fibre_channel', 'data': {'target_lun': vlun['lun'], 'target_discovered': True, - 'target_wwn': ports['FC']}} + 'target_wwn': wwns}} return info @utils.synchronized('3par', external=True) @@ -195,7 +199,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): self.common.client_login() hostname = self.common._safe_hostname(connector['host']) self.common.terminate_connection(volume, hostname, - connector['wwpns']) + wwn=connector['wwpns']) self.common.client_logout() def _create_3par_fibrechan_host(self, hostname, wwns, domain, persona_id): @@ -219,13 +223,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): return hostname - def _modify_3par_fibrechan_host(self, hostname, wwns): - # when using -add, you can not send the persona or domain options - command = ['createhost', '-add', hostname] - for wwn in wwns: - command.append(wwn) + def _modify_3par_fibrechan_host(self, hostname, wwn): + mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD, + 'FCWWNs': wwn} - out = self.common._cli_run(command) + self.common.client.modifyHost(hostname, mod_request) def _create_host(self, volume, connector): """Creates or modifies existing 3PAR host.""" @@ -235,7 +237,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): domain = self.common.get_domain(cpg) try: host = self.common._get_3par_host(hostname) - if not host['FCPaths']: + if 'FCPaths' not in host or len(host['FCPaths']) < 1: self._modify_3par_fibrechan_host(hostname, connector['wwpns']) host = self.common._get_3par_host(hostname) except hpexceptions.HTTPNotFound as ex: diff --git a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py index db3dad6fa..feb2723af 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py +++ b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py @@ -87,6 +87,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): def do_setup(self, context): self.common = self._init_common() self._check_flags() + self.common.do_setup(context) # map iscsi_ip-> ip_port # -> iqn @@ -118,14 +119,15 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): # 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(): + iscsi_ports = self.common.get_active_iscsi_target_ports() + + for port in iscsi_ports: + ip = port['IPAddr'] 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'] + 'nsp': port['nsp'], + 'iqn': port['iSCSIName'] } del temp_iscsi_ip[ip] @@ -146,8 +148,6 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 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.""" self._check_flags() @@ -233,9 +233,10 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): # now that we have a host, create the VLUN vlun = self.common.create_vlun(volume, host) + iscsi_ip = self._get_iscsi_ip(host['name']) + 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', @@ -254,7 +255,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): self.common.client_login() hostname = self.common._safe_hostname(connector['host']) self.common.terminate_connection(volume, hostname, - connector['initiator']) + iqn=connector['initiator']) self.common.client_logout() def _create_3par_iscsi_host(self, hostname, iscsi_iqn, domain, persona_id): @@ -276,9 +277,10 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): return hostname def _modify_3par_iscsi_host(self, hostname, iscsi_iqn): - # when using -add, you can not send the persona or domain options - command = ['createhost', '-iscsi', '-add', hostname, iscsi_iqn] - self.common._cli_run(command) + mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD, + 'iSCSINames': [iscsi_iqn]} + + self.common.client.modifyHost(hostname, mod_request) def _create_host(self, volume, connector): """Creates or modifies existing 3PAR host.""" @@ -289,7 +291,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): domain = self.common.get_domain(cpg) try: host = self.common._get_3par_host(hostname) - if not host['iSCSIPaths']: + if 'iSCSIPaths' not in host or len(host['iSCSIPaths']) < 1: self._modify_3par_iscsi_host(hostname, connector['initiator']) host = self.common._get_3par_host(hostname) except hpexceptions.HTTPNotFound: @@ -327,19 +329,27 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 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] + 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'] == 'true': + 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) + + # no current path find least used port + 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(nsp) + return self._get_ip_using_nsp(least_used_nsp) def _get_iscsi_nsps(self): """Return the list of candidate nsps.""" @@ -354,43 +364,29 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 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', hostname]) - 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): + def _get_least_used_nsp(self, vluns, 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']) - - # count the number of nsps (there is 1 for each active vlun) + # count the number of nsps 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() + + for vlun in vluns: + if vlun['active']: + nsp = self.common.build_nsp(vlun['portPos']) 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 + # 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 diff --git a/test-requirements.txt b/test-requirements.txt index 7a7ff9c7a..3d8e9bffc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ hacking>=0.5.6,<0.8 coverage>=3.6 discover fixtures>=0.3.14 -hp3parclient>=1.0.0 +hp3parclient>=2.0,<3.0 mock>=1.0 mox>=0.5.3 MySQL-python -- 2.45.2