From 1724c5cee71d3b32466dd9d6cabd22a3339546b4 Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Thu, 30 Jan 2014 23:44:22 -0800 Subject: [PATCH] Remove SSH code from 3PAR drivers This patch migrates all of the communication to the 3PAR array into the client library. Some of the calls to the array happen over ssh and others happen over REST. Now the drivers don't care. This allows us to change the external client library to replace SSH calls to REST calls, without the need of driver changes. Change-Id: Ia5e94a349308055381001c373b91c444860115c7 --- cinder/tests/test_hp3par.py | 1795 ++++++++--------- .../volume/drivers/san/hp/hp_3par_common.py | 209 +- cinder/volume/drivers/san/hp/hp_3par_fc.py | 28 +- cinder/volume/drivers/san/hp/hp_3par_iscsi.py | 26 +- 4 files changed, 871 insertions(+), 1187 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index ee325c4e5a2..93915d26485 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -15,327 +15,42 @@ # under the License. """Unit tests for OpenStack Cinder volume drivers.""" -import ast import mock -import mox -import shutil -import tempfile +from hp3parclient import client from hp3parclient import exceptions as hpexceptions from cinder import context from cinder import exception from cinder.openstack.common import log as logging from cinder import test -from cinder.volume import configuration as conf from cinder.volume.drivers.san.hp import hp_3par_fc as hpfcdriver from cinder.volume.drivers.san.hp import hp_3par_iscsi as hpdriver +from cinder.volume import volume_types LOG = logging.getLogger(__name__) -HP3PAR_DOMAIN = 'OpenStack', -HP3PAR_CPG = 'OpenStackCPG', +HP3PAR_CPG = 'OpenStackCPG' HP3PAR_CPG_SNAP = 'OpenStackCPGSnap' -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 - - connection_count = 0 - - volumes = [] - hosts = [] - vluns = [] - cpgs = [ - {'SAGrowth': {'LDLayout': {'diskPatterns': [{'diskType': 2}]}, - 'incrementMiB': 8192}, - 'SAUsage': {'rawTotalMiB': 24576, - 'rawUsedMiB': 768, - 'totalMiB': 8192, - 'usedMiB': 256}, - 'SDGrowth': {'LDLayout': {'RAIDType': 4, - 'diskPatterns': [{'diskType': 2}]}, - 'incrementMiB': 32768}, - 'SDUsage': {'rawTotalMiB': 49152, - 'rawUsedMiB': 1023, - 'totalMiB': 36864, - 'usedMiB': 768}, - 'UsrUsage': {'rawTotalMiB': 57344, - 'rawUsedMiB': 43349, - 'totalMiB': 43008, - 'usedMiB': 32512}, - 'additionalStates': [], - 'degradedStates': [], - 'domain': HP3PAR_DOMAIN, - 'failedStates': [], - 'id': 5, - 'name': HP3PAR_CPG, - 'numFPVVs': 2, - 'numTPVVs': 0, - 'state': 1, - 'uuid': '29c214aa-62b9-41c8-b198-543f6cf24edf'}] - - def __init__(self, api_url): - self.api_url = api_url - self.volumes = [] - self.hosts = [] - self.vluns = [] - - def debug_rest(self, flag): - self.debug = flag - - def login(self, username, password, optional=None): - self.connection_count += 1 - return None - - def logout(self): - if self.connection_count < 1: - raise hpexceptions.CommandError('No connection to log out.') - self.connection_count -= 1 - return None - - def getVolumes(self): - return self.volumes - - def getVolume(self, name): - if self.volumes: - for volume in self.volumes: - if volume['name'] == name: - return volume - - msg = {'code': 'NON_EXISTENT_HOST', - 'desc': "VOLUME '%s' was not found" % name} - raise hpexceptions.HTTPNotFound(msg) - - def createVolume(self, name, cpgName, sizeMiB, optional=None): - new_vol = {'additionalStates': [], - 'adminSpace': {'freeMiB': 0, - 'rawReservedMiB': 384, - 'reservedMiB': 128, - 'usedMiB': 128}, - 'baseId': 115, - 'comment': optional['comment'], - 'copyType': 1, - 'creationTime8601': '2012-10-22T16:37:57-07:00', - 'creationTimeSec': 1350949077, - 'degradedStates': [], - 'domain': HP3PAR_DOMAIN, - 'failedStates': [], - 'id': 115, - 'name': name, - 'policies': {'caching': True, - 'oneHost': False, - 'staleSS': True, - 'system': False, - 'zeroDetect': False}, - 'provisioningType': 1, - 'readOnly': False, - 'sizeMiB': sizeMiB, - 'snapCPG': optional['snapCPG'], - 'snapshotSpace': {'freeMiB': 0, - 'rawReservedMiB': 683, - 'reservedMiB': 512, - 'usedMiB': 512}, - 'ssSpcAllocLimitPct': 0, - 'ssSpcAllocWarningPct': 0, - 'state': 1, - 'userCPG': cpgName, - 'userSpace': {'freeMiB': 0, - 'rawReservedMiB': 41984, - 'reservedMiB': 31488, - 'usedMiB': 31488}, - 'usrSpcAllocLimitPct': 0, - 'usrSpcAllocWarningPct': 0, - 'uuid': '1e7daee4-49f4-4d07-9ab8-2b6a4319e243', - 'wwn': '50002AC00073383D'} - self.volumes.append(new_vol) - return None - - def deleteVolume(self, name): - volume = self.getVolume(name) - self.volumes.remove(volume) - - def createSnapshot(self, name, copyOfName, optional=None): - new_snap = {'additionalStates': [], - 'adminSpace': {'freeMiB': 0, - 'rawReservedMiB': 0, - 'reservedMiB': 0, - 'usedMiB': 0}, - 'baseId': 342, - 'comment': optional['comment'], - 'copyOf': copyOfName, - 'copyType': 3, - 'creationTime8601': '2012-11-09T15:13:28-08:00', - 'creationTimeSec': 1352502808, - 'degradedStates': [], - 'domain': HP3PAR_DOMAIN, - 'expirationTime8601': '2012-11-09T17:13:28-08:00', - 'expirationTimeSec': 1352510008, - 'failedStates': [], - 'id': 343, - 'name': name, - 'parentId': 342, - 'policies': {'caching': True, - 'oneHost': False, - 'staleSS': True, - 'system': False, - 'zeroDetect': False}, - 'provisioningType': 3, - 'readOnly': True, - 'retentionTime8601': '2012-11-09T16:13:27-08:00', - 'retentionTimeSec': 1352506407, - 'sizeMiB': 256, - 'snapCPG': HP3PAR_CPG_SNAP, - 'snapshotSpace': {'freeMiB': 0, - 'rawReservedMiB': 0, - 'reservedMiB': 0, - 'usedMiB': 0}, - 'ssSpcAllocLimitPct': 0, - 'ssSpcAllocWarningPct': 0, - 'state': 1, - 'userCPG': HP3PAR_CPG, - 'userSpace': {'freeMiB': 0, - 'rawReservedMiB': 0, - 'reservedMiB': 0, - 'usedMiB': 0}, - 'usrSpcAllocLimitPct': 0, - 'usrSpcAllocWarningPct': 0, - 'uuid': 'd7a40b8f-2511-46a8-9e75-06383c826d19', - 'wwn': '50002AC00157383D'} - self.volumes.append(new_snap) - return None - - def deleteSnapshot(self, name): - volume = self.getVolume(name) - self.volumes.remove(volume) - - def createCPG(self, name, optional=None): - cpg = {'SAGrowth': {'LDLayout': {'diskPatterns': [{'diskType': 2}]}, - 'incrementMiB': 8192}, - 'SAUsage': {'rawTotalMiB': 24576, - 'rawUsedMiB': 768, - 'totalMiB': 8192, - 'usedMiB': 256}, - 'SDGrowth': {'LDLayout': {'RAIDType': 4, - 'diskPatterns': [{'diskType': 2}]}, - 'incrementMiB': 32768}, - 'SDUsage': {'rawTotalMiB': 49152, - 'rawUsedMiB': 1023, - 'totalMiB': 36864, - 'usedMiB': 768}, - 'UsrUsage': {'rawTotalMiB': 57344, - 'rawUsedMiB': 43349, - 'totalMiB': 43008, - 'usedMiB': 32512}, - 'additionalStates': [], - 'degradedStates': [], - 'domain': HP3PAR_DOMAIN, - 'failedStates': [], - 'id': 1, - 'name': name, - 'numFPVVs': 2, - 'numTPVVs': 0, - 'state': 1, - 'uuid': '29c214aa-62b9-41c8-b198-000000000000'} - - new_cpg = cpg.copy() - new_cpg.update(optional) - self.cpgs.append(new_cpg) - - def getCPGs(self): - return self.cpgs - - def getCPG(self, name): - if self.cpgs: - for cpg in self.cpgs: - if cpg['name'] == name: - return cpg - - msg = {'code': 'NON_EXISTENT_HOST', - 'desc': "CPG '%s' was not found" % name} - raise hpexceptions.HTTPNotFound(msg) - - def deleteCPG(self, name): - cpg = self.getCPG(name) - self.cpgs.remove(cpg) - - def createVLUN(self, volumeName, lun, hostname=None, - portPos=None, noVcn=None, - overrideLowerPriority=None): - - vlun = {'active': False, - 'failedPathInterval': 0, - 'failedPathPol': 1, - 'hostname': hostname, - 'lun': lun, - 'multipathing': 1, - 'portPos': portPos, - 'type': 4, - 'volumeName': volumeName, - 'volumeWWN': '50002AC00077383D'} - self.vluns.append(vlun) - return None - - def deleteVLUN(self, name, lunID, hostname=None, port=None): - vlun = self.getVLUN(name) - self.vluns.remove(vlun) - - def getVLUNs(self): - return self.vluns - - def getVLUN(self, volumeName): - for vlun in self.vluns: - if vlun['volumeName'] == volumeName: - return vlun - - msg = {'code': 'NON_EXISTENT_HOST', - '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 +HP3PAR_USER_NAME = 'testUser' +HP3PAR_USER_PASS = 'testPassword' +HP3PAR_SAN_IP = '2.2.2.2' +HP3PAR_SAN_SSH_PORT = 999 +HP3PAR_SAN_SSH_CON_TIMEOUT = 44 +HP3PAR_SAN_SSH_PRIVATE = 'foobar' class HP3PARBaseDriver(): - VOLUME_ID = "d03338a9-9115-48a3-8dfc-35cdfcdc15a7" - CLONE_ID = "d03338a9-9115-48a3-8dfc-000000000000" - VOLUME_NAME = "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7" - SNAPSHOT_ID = "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31" - SNAPSHOT_NAME = "snapshot-2f823bdc-e36e-4dc8-bd15-de1c7a28ff31" - VOLUME_3PAR_NAME = "osv-0DM4qZEVSKON-DXN-NwVpw" - SNAPSHOT_3PAR_NAME = "oss-L4I73ONuTci9Fd4ceij-MQ" - FAKE_HOST = "fakehost" + VOLUME_ID = 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7' + CLONE_ID = 'd03338a9-9115-48a3-8dfc-000000000000' + VOLUME_NAME = 'volume-' + VOLUME_ID + VOLUME_NAME_3PAR = 'osv-0DM4qZEVSKON-DXN-NwVpw' + SNAPSHOT_ID = '2f823bdc-e36e-4dc8-bd15-de1c7a28ff31' + SNAPSHOT_NAME = 'snapshot-2f823bdc-e36e-4dc8-bd15-de1c7a28ff31' + VOLUME_3PAR_NAME = 'osv-0DM4qZEVSKON-DXN-NwVpw' + SNAPSHOT_3PAR_NAME = 'oss-L4I73ONuTci9Fd4ceij-MQ' + FAKE_HOST = 'fakehost' USER_ID = '2689d9a913974c008b1d859013f23607' PROJECT_ID = 'fac88235b9d64685a3530f73e490348f' VOLUME_ID_SNAP = '761fc5e5-5191-4ec7-aeba-33e36de44156' @@ -387,11 +102,13 @@ class HP3PARBaseDriver(): 'display_name': 'fakesnap', 'display_description': FAKE_DESC} + wwn = ["123456789012345", "123456789054321"] + connector = {'ip': '10.0.0.2', 'initiator': 'iqn.1993-08.org.debian:01:222', - 'wwpns': ["123456789012345", "123456789054321"], + 'wwpns': [wwn[0], wwn[1]], 'wwnns': ["223456789012345", "223456789054321"], - 'host': 'fakehost'} + 'host': FAKE_HOST} volume_type = {'name': 'gold', 'deleted': False, @@ -401,167 +118,153 @@ class HP3PARBaseDriver(): 'deleted_at': None, 'id': 'gold'} + cpgs = [ + {'SAGrowth': {'LDLayout': {'diskPatterns': [{'diskType': 2}]}, + 'incrementMiB': 8192}, + 'SAUsage': {'rawTotalMiB': 24576, + 'rawUsedMiB': 768, + 'totalMiB': 8192, + 'usedMiB': 256}, + 'SDGrowth': {'LDLayout': {'RAIDType': 4, + 'diskPatterns': [{'diskType': 2}]}, + 'incrementMiB': 32768}, + 'SDUsage': {'rawTotalMiB': 49152, + 'rawUsedMiB': 1023, + 'totalMiB': 36864, + 'usedMiB': 768}, + 'UsrUsage': {'rawTotalMiB': 57344, + 'rawUsedMiB': 43349, + 'totalMiB': 43008, + 'usedMiB': 32512}, + 'additionalStates': [], + 'degradedStates': [], + 'failedStates': [], + 'id': 5, + 'name': HP3PAR_CPG, + 'numFPVVs': 2, + 'numTPVVs': 0, + 'state': 1, + 'uuid': '29c214aa-62b9-41c8-b198-543f6cf24edf'}] + def setup_configuration(self): - configuration = mox.MockObject(conf.Configuration) + configuration = mock.Mock() configuration.hp3par_debug = False - configuration.hp3par_username = 'testUser' - configuration.hp3par_password = 'testPassword' + configuration.hp3par_username = HP3PAR_USER_NAME + configuration.hp3par_password = HP3PAR_USER_PASS 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.san_ip = HP3PAR_SAN_IP + configuration.san_login = HP3PAR_USER_NAME + configuration.san_password = HP3PAR_USER_PASS + configuration.san_ssh_port = HP3PAR_SAN_SSH_PORT + configuration.ssh_conn_timeout = HP3PAR_SAN_SSH_CON_TIMEOUT + configuration.san_private_key = HP3PAR_SAN_SSH_PRIVATE 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, "_set_connections", - self.fake_set_connections) - 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(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - self.fake_get_cpg) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "get_volume_settings_from_type", - self.fake_get_volume_settings_from_type) - self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "get_domain", - self.fake_get_domain) + @mock.patch( + 'hp3parclient.client.HP3ParClient', + spec=True, + PORT_MODE_TARGET=client.HP3ParClient.PORT_MODE_TARGET, + PORT_STATE_READY=client.HP3ParClient.PORT_STATE_READY, + PORT_PROTO_ISCSI=client.HP3ParClient.PORT_PROTO_ISCSI, + PORT_PROTO_FC=client.HP3ParClient.PORT_PROTO_FC, + HOST_EDIT_ADD=client.HP3ParClient.HOST_EDIT_ADD) + def setup_mock_client(self, _m_client, driver, conf=None, m_conf=None): - def clear_mox(self): - self.mox.ResetAll() - self.stubs.UnsetAll() + _m_client = _m_client.return_value + if m_conf is not None: + _m_client.configure_mock(**m_conf) - def fake_create_client(self): - return FakeHP3ParClient(self.driver.configuration.hp3par_api_url) + if conf is None: + conf = self.setup_configuration() + self.driver = driver(configuration=conf) + self.driver.do_setup(None) + return _m_client - def fake_get_cpg(self, volume, allowSnap=False): - return HP3PAR_CPG + def test_create_volume(self): - def fake_set_connections(self): - return + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + self.driver.create_volume(self.volume) + comment = ( + '{"display_name": "Foo Volume", "type": "OpenStack",' + ' "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",' + ' "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}') + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.createVolume( + self.VOLUME_3PAR_NAME, + HP3PAR_CPG, + 1907, { + 'comment': comment, + 'tpvv': True, + 'snapCPG': HP3PAR_CPG_SNAP}), + mock.call.logout()] - def fake_get_domain(self, cpg): - return HP3PAR_DOMAIN + mock_client.assert_has_calls(expected) - def fake_extend_volume(self, volume, new_size): - vol = self.driver.common.client.getVolume(volume['name']) - old_size = vol['sizeMiB'] - option = {'comment': vol['comment'], 'snapCPG': vol['snapCPG']} - self.driver.common.client.deleteVolume(volume['name']) - self.driver.common.client.createVolume(vol['name'], - vol['userCPG'], - new_size, option) + @mock.patch.object(volume_types, 'get_volume_type') + def test_create_volume_qos(self, _mock_volume_types): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - def fake_get_3par_host(self, hostname): - if hostname not in self._hosts: - msg = {'code': 'NON_EXISTENT_HOST', - 'desc': "HOST '%s' was not found" % hostname} - raise hpexceptions.HTTPNotFound(msg) - else: - return self._hosts[hostname] - - def fake_delete_3par_host(self, hostname): - if hostname not in self._hosts: - msg = {'code': 'NON_EXISTENT_HOST', - 'desc': "HOST '%s' was not found" % hostname} - raise hpexceptions.HTTPNotFound(msg) - else: - del self._hosts[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 - ports.append(self.FAKE_ISCSI_PORT) - return {'members': ports} - - def fake_get_volume_type(self, type_id): - return self.volume_type - - def fake_get_qos_by_volume_type(self, volume_type): - return self.QOS - - def fake_add_volume_to_volume_set(self, volume, volume_name, - cpg, vvs_name, qos): - return volume - - def fake_copy_volume(self, src_name, dest_name, cpg=None, - snap_cpg=None, tpvv=True): - pass - - def fake_get_volume_stats(self, vol_name): - return "normal" - - def fake_get_volume_settings_from_type(self, volume): - return {'cpg': HP3PAR_CPG, + _mock_volume_types.return_value = { + 'name': 'gold', + 'extra_specs': { + 'cpg': HP3PAR_CPG, 'snap_cpg': HP3PAR_CPG_SNAP, 'vvs_name': self.VVS_NAME, 'qos': self.QOS, 'tpvv': True, - 'volume_type': self.volume_type} + 'volume_type': self.volume_type}} - def fake_get_volume_settings_from_type_noqos(self, volume): - return {'cpg': HP3PAR_CPG, - 'snap_cpg': HP3PAR_CPG_SNAP, - 'vvs_name': None, - 'qos': None, - 'tpvv': True, - 'volume_type': None} - - def test_create_volume(self): - self.flags(lock_path=self.tempdir) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "get_volume_settings_from_type", - self.fake_get_volume_settings_from_type_noqos) - self.driver.create_volume(self.volume) - volume = self.driver.common.client.getVolume(self.VOLUME_3PAR_NAME) - self.assertEqual(volume['name'], self.VOLUME_3PAR_NAME) - - def test_create_volume_qos(self): - self.flags(lock_path=self.tempdir) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "get_volume_settings_from_type", - self.fake_get_volume_settings_from_type) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "_add_volume_to_volume_set", - self.fake_add_volume_to_volume_set) self.driver.create_volume(self.volume_qos) - volume = self.driver.common.client.getVolume(self.VOLUME_3PAR_NAME) + comment = ( + '{"volume_type_name": "gold", "display_name": "Foo Volume"' + ', "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7' + '", "volume_type_id": "gold", "volume_id": "d03338a9-91' + '15-48a3-8dfc-35cdfcdc15a7", "qos": {}, "type": "OpenStack"}') - self.assertEqual(volume['name'], self.VOLUME_3PAR_NAME) - self.assertNotIn(self.QOS, dict(ast.literal_eval(volume['comment']))) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.createVolume( + self.VOLUME_3PAR_NAME, + HP3PAR_CPG, + 1907, { + 'comment': comment, + 'tpvv': True, + 'snapCPG': HP3PAR_CPG_SNAP}), + mock.call.logout()] + + mock_client.assert_has_calls(expected) def test_delete_volume(self): - self.flags(lock_path=self.tempdir) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "get_volume_settings_from_type", - self.fake_get_volume_settings_from_type) + + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.delete_volume(self.volume) - self.assertRaises(hpexceptions.HTTPNotFound, - self.driver.common.client.getVolume, - self.VOLUME_ID) + + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.deleteVolume(self.VOLUME_3PAR_NAME), + mock.call.logout()] + + mock_client.assert_has_calls(expected) def test_create_cloned_volume(self): - self.flags(lock_path=self.tempdir) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "get_volume_settings_from_type", - self.fake_get_volume_settings_from_type) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_copy_volume", - self.fake_copy_volume) + + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() volume = {'name': HP3PARBaseDriver.VOLUME_NAME, 'id': HP3PARBaseDriver.CLONE_ID, 'display_name': 'Foo Volume', @@ -572,15 +275,38 @@ class HP3PARBaseDriver(): model_update = self.driver.create_cloned_volume(volume, src_vref) self.assertIsNotNone(model_update) - @mock.patch.object(hpdriver.hpcommon.HP3PARCommon, '_run_ssh') - def test_attach_volume(self, mock_run_ssh): - mock_run_ssh.side_effect = [[CLI_CR, ''], Exception('Custom ex')] + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.copyVolume( + self.VOLUME_3PAR_NAME, + 'osv-0DM4qZEVSKON-AAAAAAAAA', + HP3PAR_CPG, + HP3PAR_CPG_SNAP, True), + mock.call.logout()] + + mock_client.assert_has_calls(expected) + + def test_attach_volume(self): + + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.attach_volume(context.get_admin_context(), self.volume, 'abcdef', 'newhost', '/dev/vdb') - self.assertTrue(mock_run_ssh.called) + + expected = [ + mock.call.setVolumeMetaData( + self.VOLUME_3PAR_NAME, + 'HPQ-CS-instance_uuid', + 'abcdef')] + + mock_client.assert_has_calls(expected) + + # test the exception + mock_client.setVolumeMetaData.side_effect = Exception('Custom ex') self.assertRaises(exception.CinderException, self.driver.attach_volume, context.get_admin_context(), @@ -589,46 +315,73 @@ class HP3PARBaseDriver(): 'newhost', '/dev/vdb') - @mock.patch.object(hpdriver.hpcommon.HP3PARCommon, '_run_ssh') - def test_detach_volume(self, mock_run_ssh): - mock_run_ssh.side_effect = [[CLI_CR, ''], Exception('Custom ex')] + def test_detach_volume(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.detach_volume(context.get_admin_context(), self.volume) - self.assertTrue(mock_run_ssh.called) + expected = [ + mock.call.removeVolumeMetaData( + self.VOLUME_3PAR_NAME, + 'HPQ-CS-instance_uuid')] + + mock_client.assert_has_calls(expected) + + # test the exception + mock_client.removeVolumeMetaData.side_effect = Exception('Custom ex') self.assertRaises(exception.CinderException, self.driver.detach_volume, context.get_admin_context(), self.volume) def test_create_snapshot(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.create_snapshot(self.snapshot) - # check to see if the snapshot was created - snap_vol = self.driver.common.client.getVolume(self.SNAPSHOT_3PAR_NAME) - self.assertEqual(snap_vol['name'], self.SNAPSHOT_3PAR_NAME) + commet = ( + '{"volume_id": "761fc5e5-5191-4ec7-aeba-33e36de44156",' + ' "display_name": "fakesnap",' + ' "description": "test description name",' + ' "volume_name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}') + + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.createSnapshot( + 'oss-L4I73ONuTci9Fd4ceij-MQ', + 'osv-dh-F5VGRTseuujPjbeRBVg', + { + 'comment': commet, + 'readOnly': True}), + mock.call.logout()] + + mock_client.assert_has_calls(expected) def test_delete_snapshot(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - self.driver.create_snapshot(self.snapshot) - #make sure it exists first - vol = self.driver.common.client.getVolume(self.SNAPSHOT_3PAR_NAME) - self.assertEqual(vol['name'], self.SNAPSHOT_3PAR_NAME) self.driver.delete_snapshot(self.snapshot) - # the snapshot should be deleted now - self.assertRaises(hpexceptions.HTTPNotFound, - self.driver.common.client.getVolume, - self.SNAPSHOT_3PAR_NAME) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.deleteVolume('oss-L4I73ONuTci9Fd4ceij-MQ'), + mock.call.logout()] + + mock_client.assert_has_calls(expected) def test_delete_snapshot_in_use(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.create_snapshot(self.snapshot) self.driver.create_volume_from_snapshot(self.volume, self.snapshot) ex = hpexceptions.HTTPConflict("In use") - self.driver.common.client.deleteVolume = mock.Mock(side_effect=ex) + mock_client.deleteVolume = mock.Mock(side_effect=ex) # Deleting the snapshot that a volume is dependent on should fail self.assertRaises(exception.SnapshotIsBusy, @@ -636,11 +389,27 @@ class HP3PARBaseDriver(): self.snapshot) def test_create_volume_from_snapshot(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() self.driver.create_volume_from_snapshot(self.volume, self.snapshot) - snap_vol = self.driver.common.client.getVolume(self.VOLUME_3PAR_NAME) - self.assertEqual(snap_vol['name'], self.VOLUME_3PAR_NAME) + comment = ( + '{"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",' + ' "display_name": "Foo Volume",' + ' "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}') + + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.createSnapshot( + self.VOLUME_3PAR_NAME, + 'oss-L4I73ONuTci9Fd4ceij-MQ', + { + 'comment': comment, + 'readOnly': False}), + mock.call.logout()] + + mock_client.assert_has_calls(expected) volume = self.volume.copy() volume['size'] = 1 @@ -648,20 +417,37 @@ class HP3PARBaseDriver(): self.driver.create_volume_from_snapshot, volume, self.snapshot) - def test_create_volume_from_snapshot_qos(self): - self.flags(lock_path=self.tempdir) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_get_volume_type", - self.fake_get_volume_type) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "_get_qos_by_volume_type", - self.fake_get_qos_by_volume_type) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, - "_add_volume_to_volume_set", - self.fake_add_volume_to_volume_set) + @mock.patch.object(volume_types, 'get_volume_type') + def test_create_volume_from_snapshot_qos(self, _mock_volume_types): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + _mock_volume_types.return_value = { + 'name': 'gold', + 'extra_specs': { + 'cpg': HP3PAR_CPG, + 'snap_cpg': HP3PAR_CPG_SNAP, + 'vvs_name': self.VVS_NAME, + 'qos': self.QOS, + 'tpvv': True, + 'volume_type': self.volume_type}} self.driver.create_volume_from_snapshot(self.volume_qos, self.snapshot) - snap_vol = self.driver.common.client.getVolume(self.VOLUME_3PAR_NAME) - self.assertEqual(snap_vol['name'], self.VOLUME_3PAR_NAME) - self.assertNotIn(self.QOS, dict(ast.literal_eval(snap_vol['comment']))) + + comment = ( + '{"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",' + ' "display_name": "Foo Volume",' + ' "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}') + + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.createSnapshot( + self.VOLUME_3PAR_NAME, + 'oss-L4I73ONuTci9Fd4ceij-MQ', { + 'comment': comment, + 'readOnly': False}), + mock.call.logout()] + + mock_client.assert_has_calls(expected) volume = self.volume.copy() volume['size'] = 1 @@ -670,105 +456,180 @@ class HP3PARBaseDriver(): volume, self.snapshot) def test_terminate_connection(self): - self.flags(lock_path=self.tempdir) - #setup the connections - self.driver.initialize_connection(self.volume, self.connector) - vlun = self.driver.common.client.getVLUN(self.VOLUME_3PAR_NAME) - self.assertEqual(vlun['volumeName'], self.VOLUME_3PAR_NAME) - self.driver.terminate_connection(self.volume, self.connector, - force=True) - # vlun should be gone. - self.assertRaises(hpexceptions.HTTPNotFound, - self.driver.common.client.getVLUN, - self.VOLUME_3PAR_NAME) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVLUN.return_value = {'lun': None, 'type': 0} - @mock.patch.object(hpdriver.hpcommon.HP3PARCommon, '_run_ssh') - def test_update_volume_key_value_pair(self, mock_run_ssh): - mock_run_ssh.return_value = [CLI_CR, ''] - self.assertEqual( - self.driver.common.update_volume_key_value_pair(self.volume, - 'a', - 'b'), - None) - update_cmd = ['setvv', '-setkv', 'a=b', self.VOLUME_3PAR_NAME] - mock_run_ssh.assert_called_once_with(update_cmd, False) + self.driver.terminate_connection( + self.volume, + self.connector, + force=True) + + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getVLUN(self.VOLUME_3PAR_NAME), + mock.call.deleteVLUN( + self.VOLUME_3PAR_NAME, + None, + self.FAKE_HOST), + mock.call.deleteHost(self.FAKE_HOST), + mock.call.logout()] + + mock_client.assert_has_calls(expected) + + def test_update_volume_key_value_pair(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + + key = 'a' + value = 'b' + self.driver.common.update_volume_key_value_pair( + self.volume, + key, + value) + + expected = [ + mock.call.setVolumeMetaData(self.VOLUME_3PAR_NAME, key, value)] + + mock_client.assert_has_calls(expected) + + # check exception + mock_client.setVolumeMetaData.side_effect = Exception('fake') self.assertRaises(exception.VolumeBackendAPIException, self.driver.common.update_volume_key_value_pair, self.volume, None, 'b') - @mock.patch.object(hpdriver.hpcommon.HP3PARCommon, '_run_ssh') - def test_clear_volume_key_value_pair(self, mock_run_ssh): - mock_run_ssh.side_effect = [[CLI_CR, ''], Exception('Custom ex')] - self.assertEqual( - self.driver.common.clear_volume_key_value_pair(self.volume, 'a'), - None) - clear_cmd = ['setvv', '-clrkey', 'a', self.VOLUME_3PAR_NAME] - mock_run_ssh.assert_called_once_with(clear_cmd, False) + def test_clear_volume_key_value_pair(self): + + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + + key = 'a' + self.driver.common.clear_volume_key_value_pair(self.volume, key) + + expected = [ + mock.call.removeVolumeMetaData(self.VOLUME_3PAR_NAME, key)] + + mock_client.assert_has_calls(expected) + + # check the exception + mock_client.removeVolumeMetaData.side_effect = Exception('fake') self.assertRaises(exception.VolumeBackendAPIException, self.driver.common.clear_volume_key_value_pair, self.volume, None) def test_extend_volume(self): - self.flags(lock_path=self.tempdir) - self.stubs.UnsetAll() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "extend_volume", - self.fake_extend_volume) - option = {'comment': '', 'snapCPG': HP3PAR_CPG_SNAP} - self.driver.common.client.createVolume(self.volume['name'], - HP3PAR_CPG, - self.volume['size'], - option) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + grow_size = 3 old_size = self.volume['size'] - volume = self.driver.common.client.getVolume(self.volume['name']) - self.driver.extend_volume(volume, str(old_size + 1)) - vol = self.driver.common.client.getVolume(self.volume['name']) - self.assertEqual(vol['sizeMiB'], str(old_size + 1)) + new_size = old_size + grow_size + self.driver.extend_volume(self.volume, str(new_size)) + + expected = [ + mock.call.growVolume(self.VOLUME_3PAR_NAME, grow_size)] + + mock_client.assert_has_calls(expected) + + def test_get_ports(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getPorts.return_value = { + 'members': [{ + 'portPos': {'node': 0, 'slot': 8, 'cardPort': 2}, + 'protocol': 2, + 'IPAddr': '10.10.120.252', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21810002ac00383d', + '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}}]} + + ports = self.driver.common.get_ports()['members'] + self.assertEqual(len(ports), 3) class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): - _hosts = {} + properties = { + 'driver_volume_type': 'fibre_channel', + 'data': { + 'target_lun': 90, + 'target_wwn': ['0987654321234', '123456789000987'], + 'target_discovered': True}} def setUp(self): - self.tempdir = tempfile.mkdtemp() super(TestHP3PARFCDriver, self).setUp() - self.setup_driver(self.setup_configuration()) - self.setup_fakes() - - def setup_fakes(self): - super(TestHP3PARFCDriver, self).setup_fakes() - self.stubs.Set(hpfcdriver.HP3PARFCDriver, - "_create_3par_fibrechan_host", - self.fake_create_3par_fibrechan_host) def tearDown(self): - shutil.rmtree(self.tempdir) - self.assertEqual(0, self.driver.common.client.connection_count, - 'Leaked hp3parclient connection.') super(TestHP3PARFCDriver, self).tearDown() - def setup_driver(self, configuration): - self.driver = hpfcdriver.HP3PARFCDriver(configuration=configuration) + def setup_driver(self, config=None, mock_conf=None): - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client", - self.fake_create_client) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_set_connections", - self.fake_set_connections) - self.driver.do_setup(None) + mock_client = self.setup_mock_client( + conf=config, + m_conf=mock_conf, + driver=hpfcdriver.HP3PARFCDriver) - def fake_create_3par_fibrechan_host(self, hostname, wwn, - domain, persona_id): - host = {'FCPaths': [{'driverVersion': None, + expected = [ + mock.call.setSSHOptions( + HP3PAR_SAN_IP, + HP3PAR_USER_NAME, + HP3PAR_USER_PASS, + privatekey=HP3PAR_SAN_SSH_PRIVATE, + port=HP3PAR_SAN_SSH_PORT, + conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT), + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getCPG(HP3PAR_CPG), + mock.call.setHighConnections(), + mock.call.logout()] + mock_client.assert_has_calls(expected) + mock_client.reset_mock() + return mock_client + + def test_initialize_connection(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('fake'), + {'name': self.FAKE_HOST, + 'FCPaths': [{'driverVersion': None, 'firmwareVersion': None, 'hostSpeed': 0, 'model': None, 'portPos': {'cardPort': 1, 'node': 1, 'slot': 2}, 'vendor': None, - 'wwn': wwn[0]}, + 'wwn': self.wwn[0]}, {'driverVersion': None, 'firmwareVersion': None, 'hostSpeed': 0, @@ -776,177 +637,177 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'portPos': {'cardPort': 1, 'node': 0, 'slot': 2}, 'vendor': None, - 'wwn': wwn[1]}], - 'descriptors': None, - 'domain': domain, - 'iSCSIPaths': [], - 'id': 11, - 'name': hostname} - self._hosts[hostname] = host - self.properties = {'data': - {'target_discovered': True, - 'target_lun': 186, - 'target_portal': '1.1.1.2:1234'}, - 'driver_volume_type': 'fibre_channel'} - return hostname + 'wwn': self.wwn[1]}]}] + mock_client.findHost.return_value = self.FAKE_HOST + mock_client.getVLUN.return_value = {'lun': 90} + mock_client.getPorts.return_value = { + 'members': self.FAKE_FC_PORTS + [self.FAKE_ISCSI_PORT]} - def test_initialize_connection(self): - self.flags(lock_path=self.tempdir) result = self.driver.initialize_connection(self.volume, self.connector) - self.assertEqual(result['driver_volume_type'], 'fibre_channel') - # we should have a host and a vlun now. - host = self.fake_get_3par_host(self.FAKE_HOST) - self.assertEqual(self.FAKE_HOST, host['name']) - self.assertEqual(HP3PAR_DOMAIN, host['domain']) - vlun = self.driver.common.client.getVLUN(self.VOLUME_3PAR_NAME) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.ANY, + mock.call.getHost(self.FAKE_HOST), + mock.call.createVLUN( + 'osv-0DM4qZEVSKON-DXN-NwVpw', + auto=True, + hostname=self.FAKE_HOST), + mock.call.getVLUN('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getPorts(), + mock.call.logout()] - self.assertEqual(self.VOLUME_3PAR_NAME, vlun['volumeName']) - self.assertEqual(self.FAKE_HOST, vlun['hostname']) + mock_client.assert_has_calls(expected) + + self.assertDictMatch(result, self.properties) def test_get_volume_stats(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getCPG.return_value = self.cpgs[0] - def fake_safe_get(*args): - return "HP3PARFCDriver" - - self.stubs.Set(self.driver.configuration, 'safe_get', fake_safe_get) stats = self.driver.get_volume_stats(True) self.assertEqual(stats['storage_protocol'], 'FC') self.assertEqual(stats['total_capacity_gb'], 'infinite') self.assertEqual(stats['free_capacity_gb'], 'infinite') - #modify the CPG to have a limit - old_cpg = self.driver.common.client.getCPG(HP3PAR_CPG) - options = {'SDGrowth': {'limitMiB': 8192}} - self.driver.common.client.deleteCPG(HP3PAR_CPG) - self.driver.common.client.createCPG(HP3PAR_CPG, options) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getCPG(HP3PAR_CPG), + mock.call.logout()] + + mock_client.assert_has_calls(expected) + stats = self.driver.get_volume_stats(True) + self.assertEqual(stats['storage_protocol'], 'FC') + self.assertEqual(stats['total_capacity_gb'], 'infinite') + self.assertEqual(stats['free_capacity_gb'], 'infinite') + + cpg2 = self.cpgs[0].copy() + cpg2.update({'SDGrowth': {'limitMiB': 8192}}) + mock_client.getCPG.return_value = cpg2 const = 0.0009765625 stats = self.driver.get_volume_stats(True) self.assertEqual(stats['storage_protocol'], 'FC') total_capacity_gb = 8192 * const self.assertEqual(stats['total_capacity_gb'], total_capacity_gb) - free_capacity_gb = int((8192 - old_cpg['UsrUsage']['usedMiB']) * const) + free_capacity_gb = int( + (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const) self.assertEqual(stats['free_capacity_gb'], free_capacity_gb) self.driver.common.client.deleteCPG(HP3PAR_CPG) self.driver.common.client.createCPG(HP3PAR_CPG, {}) def test_create_host(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - #record - self.clear_mox() - self.stubs.Set(hpfcdriver.hpcommon.HP3PARCommon, "get_cpg", - self.fake_get_cpg) - self.stubs.Set(hpfcdriver.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) - - 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, '']) - - getHost('fakehost').AndReturn({'name': self.FAKE_HOST, - 'FCPaths': [{'wwn': '123456789012345'}, - {'wwn': '123456789054321'}]} - ) - self.mox.ReplayAll() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('fake'), + {'name': self.FAKE_HOST, + 'FCPaths': [{'driverVersion': None, + 'firmwareVersion': None, + 'hostSpeed': 0, + 'model': None, + 'portPos': {'cardPort': 1, 'node': 1, + 'slot': 2}, + 'vendor': None, + 'wwn': self.wwn[0]}, + {'driverVersion': None, + 'firmwareVersion': None, + 'hostSpeed': 0, + 'model': None, + 'portPos': {'cardPort': 1, 'node': 0, + 'slot': 2}, + 'vendor': None, + 'wwn': self.wwn[1]}]}] + mock_client.findHost.return_value = None + mock_client.getVLUN.return_value = {'lun': 186} host = self.driver._create_host(self.volume, self.connector) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.findHost(wwn='123456789012345'), + mock.call.findHost(wwn='123456789054321'), + mock.call.createHost( + self.FAKE_HOST, + FCWwns=['123456789012345', '123456789054321'], + optional={'domain': None, 'persona': 1}), + mock.call.getHost(self.FAKE_HOST)] + + mock_client.assert_has_calls(expected) + self.assertEqual(host['name'], self.FAKE_HOST) def test_create_invalid_host(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - #record - self.clear_mox() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - 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) - - 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', - '123456789054321']) - create_host_ret = pack(CLI_CR + - 'already used by host fakehost.foo (19)') - _run_ssh(create_host_cmd, False).AndReturn([create_host_ret, '']) - - host_ret = { - 'name': 'fakehost.foo', - 'FCPaths': [{'wwn': '123456789012345'}, - {'wwn': '123456789054321'}]} - getHost('fakehost.foo').AndReturn(host_ret) - - self.mox.ReplayAll() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('Host not found.'), { + 'name': 'fakehost.foo', + 'FCPaths': [{'wwn': '123456789012345'}, { + 'wwn': '123456789054321'}]}] + mock_client.findHost.return_value = 'fakehost.foo' host = self.driver._create_host(self.volume, self.connector) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost('fakehost'), + mock.call.findHost(wwn='123456789012345'), + mock.call.getHost('fakehost.foo')] + + mock_client.assert_has_calls(expected) + self.assertEqual(host['name'], 'fakehost.foo') def test_create_modify_host(self): - self.flags(lock_path=self.tempdir) - - #record - self.clear_mox() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - self.fake_get_cpg) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain", - self.fake_get_domain) - - getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) - self.stubs.Set(FakeHP3ParClient, "getHost", getHost) - - 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'}]} - ) - - self.mox.ReplayAll() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [{ + 'name': self.FAKE_HOST, 'FCPaths': []}, + {'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789012345'}, { + 'wwn': '123456789054321'}]}] host = self.driver._create_host(self.volume, self.connector) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost('fakehost'), + mock.call.modifyHost( + 'fakehost', { + 'FCWWNs': ['123456789012345', '123456789054321'], + 'pathOperation': 1}), + mock.call.getHost('fakehost')] + + mock_client.assert_has_calls(expected) + self.assertEqual(host['name'], self.FAKE_HOST) self.assertEqual(len(host['FCPaths']), 2) def test_modify_host_with_new_wwn(self): - self.flags(lock_path=self.tempdir) - self.clear_mox() - - hpdriver.hpcommon.HP3PARCommon.get_cpg = mock.Mock( - return_value=self.fake_get_cpg) - hpdriver.hpcommon.HP3PARCommon.get_domain = mock.Mock( - return_value=self.fake_get_domain) - - # set up the getHost mock - self.driver.common.client.getHost = mock.Mock() - # define the return values for the 2 calls + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} getHost_ret1 = { 'name': self.FAKE_HOST, 'FCPaths': [{'wwn': '123456789054321'}]} @@ -954,36 +815,30 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'name': self.FAKE_HOST, 'FCPaths': [{'wwn': '123456789012345'}, {'wwn': '123456789054321'}]} - self.driver.common.client.getHost.side_effect = [ - getHost_ret1, getHost_ret2] - - # setup the modifyHost mock - self.driver.common.client.modifyHost = mock.Mock() + mock_client.getHost.side_effect = [getHost_ret1, getHost_ret2] host = self.driver._create_host(self.volume, self.connector) - # mock assertions - self.driver.common.client.getHost.assert_has_calls([ - mock.call('fakehost'), - mock.call('fakehost')]) - self.driver.common.client.modifyHost.assert_called_once_with( - 'fakehost', {'FCWWNs': ['123456789012345'], 'pathOperation': 1}) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost('fakehost'), + mock.call.modifyHost( + 'fakehost', { + 'FCWWNs': ['123456789012345'], 'pathOperation': 1}), + mock.call.getHost('fakehost')] + + mock_client.assert_has_calls(expected) self.assertEqual(host['name'], self.FAKE_HOST) self.assertEqual(len(host['FCPaths']), 2) def test_modify_host_with_unknown_wwn_and_new_wwn(self): - self.flags(lock_path=self.tempdir) - self.clear_mox() - - hpdriver.hpcommon.HP3PARCommon.get_cpg = mock.Mock( - return_value=self.fake_get_cpg) - hpdriver.hpcommon.HP3PARCommon.get_domain = mock.Mock( - return_value=self.fake_get_domain) - - # set up the getHost mock - self.driver.common.client.getHost = mock.Mock() - # define the return values for the 2 calls + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} getHost_ret1 = { 'name': self.FAKE_HOST, 'FCPaths': [{'wwn': '123456789054321'}, @@ -993,20 +848,20 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 'FCPaths': [{'wwn': '123456789012345'}, {'wwn': '123456789054321'}, {'wwn': 'xxxxxxxxxxxxxxx'}]} - self.driver.common.client.getHost.side_effect = [ - getHost_ret1, getHost_ret2] - - # setup the modifyHost mock - self.driver.common.client.modifyHost = mock.Mock() + mock_client.getHost.side_effect = [getHost_ret1, getHost_ret2] host = self.driver._create_host(self.volume, self.connector) - # mock assertions - self.driver.common.client.getHost.assert_has_calls([ - mock.call('fakehost'), - mock.call('fakehost')]) - self.driver.common.client.modifyHost.assert_called_once_with( - 'fakehost', {'FCWWNs': ['123456789012345'], 'pathOperation': 1}) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost('fakehost'), + mock.call.modifyHost( + 'fakehost', { + 'FCWWNs': ['123456789012345'], 'pathOperation': 1}), + mock.call.getHost('fakehost')] + + mock_client.assert_has_calls(expected) self.assertEqual(host['name'], self.FAKE_HOST) self.assertEqual(len(host['FCPaths']), 3) @@ -1014,234 +869,213 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): - TARGET_IQN = "iqn.2000-05.com.3pardata:21810002ac00383d" + TARGET_IQN = 'iqn.2000-05.com.3pardata:21810002ac00383d' + TARGET_LUN = 186 - _hosts = {} + properties = { + 'driver_volume_type': 'iscsi', + 'data': + {'target_discovered': True, + 'target_iqn': TARGET_IQN, + 'target_lun': TARGET_LUN, + 'target_portal': '1.1.1.2:1234'}} def setUp(self): - self.tempdir = tempfile.mkdtemp() super(TestHP3PARISCSIDriver, self).setUp() - self.setup_driver(self.setup_configuration()) - self.setup_fakes() - - def setup_fakes(self): - super(TestHP3PARISCSIDriver, self).setup_fakes() - - self.stubs.Set(hpdriver.HP3PARISCSIDriver, "_create_3par_iscsi_host", - self.fake_create_3par_iscsi_host) - - #target_iqn = 'iqn.2000-05.com.3pardata:21810002ac00383d' - self.properties = {'data': - {'target_discovered': True, - 'target_iqn': self.TARGET_IQN, - 'target_lun': 186, - 'target_portal': '1.1.1.2:1234'}, - 'driver_volume_type': 'iscsi'} def tearDown(self): - shutil.rmtree(self.tempdir) - self.assertEqual(0, self.driver.common.client.connection_count, - 'Leaked hp3parclient connection.') - self._hosts = {} super(TestHP3PARISCSIDriver, self).tearDown() - def setup_driver(self, configuration, set_up_fakes=True): - self.driver = hpdriver.HP3PARISCSIDriver(configuration=configuration) + def setup_driver(self, config=None, mock_conf=None): - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_create_client", - self.fake_create_client) + # setup_mock_client default config, if necessary + if mock_conf is None: + mock_conf = { + 'getPorts.return_value': { + 'members': self.FAKE_FC_PORTS + [self.FAKE_ISCSI_PORT]}} - if set_up_fakes: - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_ports", - self.fake_get_ports) + mock_client = self.setup_mock_client( + conf=config, + m_conf=mock_conf, + driver=hpdriver.HP3PARISCSIDriver) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "_set_connections", - self.fake_set_connections) - self.driver.do_setup(None) + expected = [ + mock.call.setSSHOptions( + HP3PAR_SAN_IP, + HP3PAR_USER_NAME, + HP3PAR_USER_PASS, + privatekey=HP3PAR_SAN_SSH_PRIVATE, + port=HP3PAR_SAN_SSH_PORT, + conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT), + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getCPG(HP3PAR_CPG), + mock.call.setHighConnections(), + mock.call.logout(), + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getPorts(), + mock.call.logout()] + mock_client.assert_has_calls(expected) + mock_client.reset_mock() - def fake_create_3par_iscsi_host(self, hostname, iscsi_iqn, - domain, persona_id): - host = {'FCPaths': [], - 'descriptors': None, - 'domain': domain, - 'iSCSIPaths': [{'driverVersion': None, - 'firmwareVersion': None, - 'hostSpeed': 0, - 'ipAddr': '10.10.221.59', - 'model': None, - 'name': iscsi_iqn, - 'portPos': {'cardPort': 1, 'node': 1, - 'slot': 8}, - 'vendor': None}], - 'id': 11, - 'name': hostname} - self._hosts[hostname] = host - return hostname + return mock_client def test_initialize_connection(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('fake'), + {'name': self.FAKE_HOST}] + mock_client.findHost.return_value = self.FAKE_HOST + mock_client.getVLUN.return_value = {'lun': self.TARGET_LUN} + result = self.driver.initialize_connection(self.volume, self.connector) - self.assertEqual(result['driver_volume_type'], 'iscsi') - self.assertEqual(result['data']['target_iqn'], - self.properties['data']['target_iqn']) - self.assertEqual(result['data']['target_portal'], - self.properties['data']['target_portal']) - self.assertEqual(result['data']['target_discovered'], - self.properties['data']['target_discovered']) - # we should have a host and a vlun now. - host = self.fake_get_3par_host(self.FAKE_HOST) - self.assertEqual(self.FAKE_HOST, host['name']) - self.assertEqual(HP3PAR_DOMAIN, host['domain']) - vlun = self.driver.common.client.getVLUN(self.VOLUME_3PAR_NAME) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.findHost(iqn='iqn.1993-08.org.debian:01:222'), + mock.call.getHost(self.FAKE_HOST), + mock.call.createVLUN( + 'osv-0DM4qZEVSKON-DXN-NwVpw', + auto=True, + hostname='fakehost', + portPos={'node': 8, 'slot': 1, 'cardPort': 1}), + mock.call.getVLUN('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.logout()] - self.assertEqual(self.VOLUME_3PAR_NAME, vlun['volumeName']) - self.assertEqual(self.FAKE_HOST, vlun['hostname']) + mock_client.assert_has_calls(expected) + + self.assertDictMatch(result, self.properties) def test_get_volume_stats(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getCPG.return_value = self.cpgs[0] - def fake_safe_get(*args): - return "HP3PARFCDriver" - - self.stubs.Set(self.driver.configuration, 'safe_get', fake_safe_get) stats = self.driver.get_volume_stats(True) self.assertEqual(stats['storage_protocol'], 'iSCSI') self.assertEqual(stats['total_capacity_gb'], 'infinite') self.assertEqual(stats['free_capacity_gb'], 'infinite') - #modify the CPG to have a limit - old_cpg = self.driver.common.client.getCPG(HP3PAR_CPG) - options = {'SDGrowth': {'limitMiB': 8192}} - self.driver.common.client.deleteCPG(HP3PAR_CPG) - self.driver.common.client.createCPG(HP3PAR_CPG, options) + expected = [ + mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS), + mock.call.getCPG(HP3PAR_CPG), + mock.call.logout()] + + mock_client.assert_has_calls(expected) + + self.assertEqual(stats['storage_protocol'], 'iSCSI') + self.assertEqual(stats['total_capacity_gb'], 'infinite') + self.assertEqual(stats['free_capacity_gb'], 'infinite') + + cpg2 = self.cpgs[0].copy() + cpg2.update({'SDGrowth': {'limitMiB': 8192}}) + mock_client.getCPG.return_value = cpg2 const = 0.0009765625 stats = self.driver.get_volume_stats(True) self.assertEqual(stats['storage_protocol'], 'iSCSI') total_capacity_gb = 8192 * const self.assertEqual(stats['total_capacity_gb'], total_capacity_gb) - free_capacity_gb = int((8192 - old_cpg['UsrUsage']['usedMiB']) * const) + free_capacity_gb = int( + (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const) self.assertEqual(stats['free_capacity_gb'], free_capacity_gb) - self.driver.common.client.deleteCPG(HP3PAR_CPG) - self.driver.common.client.createCPG(HP3PAR_CPG, {}) def test_create_host(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - #record - self.clear_mox() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - 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) - - 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, '']) - - getHost('fakehost').AndReturn({'name': self.FAKE_HOST}) - self.mox.ReplayAll() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('fake'), + {'name': self.FAKE_HOST}] + mock_client.findHost.return_value = None + mock_client.getVLUN.return_value = {'lun': self.TARGET_LUN} host = self.driver._create_host(self.volume, self.connector) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.findHost(iqn='iqn.1993-08.org.debian:01:222'), + mock.call.createHost( + self.FAKE_HOST, + optional={'domain': None, 'persona': 1}, + iscsiNames=['iqn.1993-08.org.debian:01:222']), + mock.call.getHost(self.FAKE_HOST)] + + mock_client.assert_has_calls(expected) + self.assertEqual(host['name'], self.FAKE_HOST) def test_create_invalid_host(self): - self.flags(lock_path=self.tempdir) - - #record - self.clear_mox() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - 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) - - 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']) - in_use_ret = pack('\r\nalready used by host fakehost.foo ') - _run_ssh(create_host_cmd, False).AndReturn([in_use_ret, '']) - - getHost('fakehost.foo').AndReturn({'name': 'fakehost.foo'}) - self.mox.ReplayAll() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpexceptions.HTTPNotFound('Host not found.'), + {'name': 'fakehost.foo'}] + mock_client.findHost.return_value = 'fakehost.foo' host = self.driver._create_host(self.volume, self.connector) + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.findHost(iqn='iqn.1993-08.org.debian:01:222'), + mock.call.getHost('fakehost.foo')] + + mock_client.assert_has_calls(expected) + self.assertEqual(host['name'], 'fakehost.foo') def test_create_modify_host(self): - self.flags(lock_path=self.tempdir) - - #record - self.clear_mox() - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_cpg", - self.fake_get_cpg) - self.stubs.Set(hpdriver.hpcommon.HP3PARCommon, "get_domain", - self.fake_get_domain) - - getHost = self.mox.CreateMock(FakeHP3ParClient.getHost) - self.stubs.Set(FakeHP3ParClient, "getHost", getHost) - - 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() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [{ + 'name': self.FAKE_HOST, 'FCPaths': []}, { + 'name': self.FAKE_HOST, + 'FCPaths': [{'wwn': '123456789012345'}, { + 'wwn': '123456789054321'}]}] host = self.driver._create_host(self.volume, self.connector) + + expected = [ + mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'), + mock.call.getCPG(HP3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.modifyHost( + self.FAKE_HOST, + {'pathOperation': 1, + 'iSCSINames': ['iqn.1993-08.org.debian:01:222']}), + mock.call.getHost(self.FAKE_HOST)] + + mock_client.assert_has_calls(expected) + 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() - getPorts = self.mox.CreateMock(FakeHP3ParClient.getPorts) - self.stubs.Set(FakeHP3ParClient, "getPorts", getPorts) - - getPorts().AndReturn(PORTS1_RET) - self.mox.ReplayAll() - - ports = self.driver.common.get_ports()['members'] - self.assertEqual(len(ports), 3) + self.assertEqual(len(host['FCPaths']), 2) def test_get_least_used_nsp_for_host_single(self): - self.flags(lock_path=self.tempdir) - self.clear_mox() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - self.driver.common.client.getPorts = mock.Mock( - return_value=PORTS_RET) - - self.driver.common.client.getVLUNs = mock.Mock( - return_value=VLUNS1_RET) + mock_client.getPorts.return_value = PORTS_RET + mock_client.getVLUNs.return_value = VLUNS1_RET #Setup a single ISCSI IP iscsi_ips = ["10.10.220.253"] @@ -1253,14 +1087,12 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): self.assertEqual(nsp, "1:8:1") def test_get_least_used_nsp_for_host_new(self): - self.flags(lock_path=self.tempdir) - self.clear_mox() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - self.driver.common.client.getPorts = mock.Mock( - return_value=PORTS_RET) - - self.driver.common.client.getVLUNs = mock.Mock( - return_value=VLUNS1_RET) + mock_client.getPorts.return_value = PORTS_RET + mock_client.getVLUNs.return_value = VLUNS1_RET #Setup two ISCSI IPs iscsi_ips = ["10.10.220.252", "10.10.220.253"] @@ -1274,14 +1106,12 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): 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() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - self.driver.common.client.getPorts = mock.Mock( - return_value=PORTS_RET) - - self.driver.common.client.getVLUNs = mock.Mock( - return_value=VLUNS1_RET) + mock_client.getPorts.return_value = PORTS_RET + mock_client.getVLUNs.return_value = VLUNS1_RET #Setup two ISCSI IPs iscsi_ips = ["10.10.220.252", "10.10.220.253"] @@ -1298,15 +1128,12 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): 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() + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() - self.driver.common.client.getPorts = mock.Mock( - return_value=PORTS1_RET) - self.driver.common.client.getVLUNs = mock.Mock( - return_value=VLUNS5_RET) + mock_client.getPorts.return_value = PORTS1_RET + mock_client.getVLUNs.return_value = VLUNS5_RET #Setup two ISCSI IPs iscsi_ips = ["10.10.220.252", "10.10.220.253"] @@ -1319,62 +1146,101 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): self.assertEqual(nsp, "1:8:1") def test_invalid_iscsi_ip(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) - - getPorts().AndReturn(PORTS_RET) - 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() + mock_conf = { + 'getPorts.return_value': { + 'members': [{ + 'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'protocol': 2, + 'IPAddr': '10.10.220.252', + 'linkState': 4, + 'device': [], + 'iSCSIName': self.TARGET_IQN, + 'mode': 2, + 'HWAddr': '2C27D75375D2', + 'type': 8}, { + 'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'protocol': 2, + 'IPAddr': '10.10.220.253', + 'linkState': 4, + 'device': [], + 'iSCSIName': self.TARGET_IQN, + 'mode': 2, + 'HWAddr': '2C27D75375D6', + 'type': 8}]}} # no valid ip addr should be configured. self.assertRaises(exception.InvalidInput, self.setup_driver, - config, - set_up_fakes=False) + config=config, + mock_conf=mock_conf) def test_get_least_used_nsp(self): - self.flags(lock_path=self.tempdir) + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + ports = [ + {'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}] + mock_client.getVLUNs.return_value = {'members': ports} - #record - self.clear_mox() - getVLUNs = self.mox.CreateMock(FakeHP3ParClient.getVLUNs) - self.stubs.Set(FakeHP3ParClient, "getVLUNs", getVLUNs) - - getVLUNs().AndReturn(VLUNS3_RET) - getVLUNs().AndReturn(VLUNS4_RET) - getVLUNs().AndReturn(VLUNS4_RET) - - self.mox.ReplayAll() # 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') + ports = [ + {'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}] + + mock_client.getVLUNs.return_value = {'members': ports} + # 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') + ports = [ + {'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}] + + mock_client.getVLUNs.return_value = {'members': ports} + # 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') - -def pack(arg): - header = '\r\n\r\n\r\n\r\n\r\n' - footer = '\r\n\r\n\r\n' - return header + arg + footer +VLUNS5_RET = ({'members': + [{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2}, + 'active': True}, + {'portPos': {'node': 1, 'slot': 8, 'cardPort': 1}, + 'active': True}]}) PORTS_RET = ({'members': [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, @@ -1396,6 +1262,16 @@ PORTS_RET = ({'members': 'HWAddr': '2C27D75375D6', 'type': 8}]}) +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}]}) + PORTS1_RET = ({'members': [{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2}, 'protocol': 2, @@ -1423,66 +1299,3 @@ PORTS1_RET = ({'members': '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': 'bar', 'active': True}, - {'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, - 'hostname': 'bar', 'active': True}, - {'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, - 'hostname': 'fakehost', '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}]}) -VLUNS5_RET = ({'members': - [{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2}, - 'active': True}, - {'portPos': {'node': 1, 'slot': 8, '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 d32271c8a3e..04797038565 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -38,11 +38,9 @@ import ast import base64 import json import pprint -from random import randint import re import uuid -from eventlet import greenthread import hp3parclient from hp3parclient import client from hp3parclient import exceptions as hpexceptions @@ -52,14 +50,12 @@ from cinder import context from cinder import exception from cinder.openstack.common import excutils from cinder.openstack.common import log as logging -from cinder.openstack.common import processutils -from cinder import utils from cinder.volume import volume_types LOG = logging.getLogger(__name__) -MIN_CLIENT_VERSION = '2.0.0' +MIN_CLIENT_VERSION = '2.9.0' hp3par_opts = [ cfg.StrOpt('hp3par_api_url', @@ -113,10 +109,11 @@ class HP3PARCommon(object): 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 + 1.3.0 - Removed all SSH code. We rely on the hp3parclient now. """ - VERSION = "1.2.6" + VERSION = "1.3.0" stats = {} @@ -143,7 +140,6 @@ class HP3PARCommon(object): hp3par_valid_keys = ['cpg', 'snap_cpg', 'provisioning', 'persona', 'vvs'] def __init__(self, config): - self.sshpool = None self.config = config self.hosts_naming_dict = dict() self.client = None @@ -163,11 +159,20 @@ class HP3PARCommon(object): client_version = hp3parclient.version if (client_version < MIN_CLIENT_VERSION): - ex_msg = (_('Invalid hp3parclient version. Version %s or greater ' - 'required.') % MIN_CLIENT_VERSION) + ex_msg = (_('Invalid hp3parclient version found (%(found)s). ' + 'Version %(minimum)s or greater required.') + % {'found': client_version, + 'minimum': MIN_CLIENT_VERSION}) LOG.error(ex_msg) raise exception.InvalidInput(reason=ex_msg) + cl.setSSHOptions(self.config.san_ip, + self.config.san_login, + self.config.san_password, + port=self.config.san_ssh_port, + conn_timeout=self.config.ssh_conn_timeout, + privatekey=self.config.san_private_key) + return cl def client_login(self): @@ -201,7 +206,7 @@ class HP3PARCommon(object): try: # make sure the default CPG exists self.validate_cpg(self.config.hp3par_cpg) - self._set_connections() + self.client.setHighConnections() finally: self.client_logout() @@ -213,14 +218,6 @@ class HP3PARCommon(object): LOG.error(err) raise exception.InvalidInput(reason=err) - def _set_connections(self): - """Set the number of concurrent connections. - - The 3PAR WS API server has a limit of concurrent connections. - This is setting the number to the highest allowed, 15 connections. - """ - self._cli_run(['setwsapi', '-sru', 'high']) - def get_domain(self, cpg_name): try: cpg = self.client.getCPG(cpg_name) @@ -236,12 +233,12 @@ class HP3PARCommon(object): def extend_volume(self, volume, new_size): volume_name = self._get_3par_vol_name(volume['id']) - old_size = volume.size + old_size = volume['size'] growth_size = int(new_size) - old_size LOG.debug("Extending Volume %s from %s to %s, by %s GB." % (volume_name, old_size, new_size, growth_size)) try: - self._cli_run(['growvv', '-f', volume_name, '%dg' % growth_size]) + self.client.growVolume(volume_name, growth_size) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_("Error extending volume %s") % volume) @@ -299,96 +296,6 @@ class HP3PARCommon(object): capacity = int(round(capacity / MiB)) return capacity - def _cli_run(self, cmd): - """Runs a CLI command over SSH, without doing any result parsing.""" - LOG.debug("SSH CMD = %s " % cmd) - - (stdout, stderr) = self._run_ssh(cmd, False) - # we have to strip out the input and exit lines - tmp = stdout.split("\r\n") - out = tmp[5:len(tmp) - 2] - return out - - def _ssh_execute(self, ssh, cmd, check_exit_code=True): - """We have to do this in order to get CSV output from the CLI command. - - We first have to issue a command to tell the CLI that we want the - output to be formatted in CSV, then we issue the real command. - """ - LOG.debug(_('Running cmd (SSH): %s'), cmd) - - channel = ssh.invoke_shell() - stdin_stream = channel.makefile('wb') - stdout_stream = channel.makefile('rb') - stderr_stream = channel.makefile('rb') - - stdin_stream.write('''setclienv csvtable 1 -%s -exit -''' % cmd) - - # stdin.write('process_input would go here') - # stdin.flush() - - # NOTE(justinsb): This seems suspicious... - # ...other SSH clients have buffering issues with this approach - stdout = stdout_stream.read() - stderr = stderr_stream.read() - stdin_stream.close() - stdout_stream.close() - stderr_stream.close() - - exit_status = channel.recv_exit_status() - - # exit_status == -1 if no exit code was returned - if exit_status != -1: - LOG.debug(_('Result was %s') % exit_status) - if check_exit_code and exit_status != 0: - msg = _("command %s failed") % cmd - LOG.error(msg) - raise processutils.ProcessExecutionError(exit_code=exit_status, - stdout=stdout, - stderr=stderr, - cmd=cmd) - channel.close() - return (stdout, stderr) - - def _run_ssh(self, cmd_list, check_exit=True, attempts=1): - utils.check_ssh_injection(cmd_list) - command = ' '. join(cmd_list) - - if not self.sshpool: - self.sshpool = utils.SSHPool(self.config.san_ip, - self.config.san_ssh_port, - self.config.ssh_conn_timeout, - self.config.san_login, - password=self.config.san_password, - privatekey= - self.config.san_private_key, - min_size= - self.config.ssh_min_pool_conn, - max_size= - self.config.ssh_max_pool_conn) - try: - total_attempts = attempts - with self.sshpool.item() as ssh: - while attempts > 0: - attempts -= 1 - try: - return self._ssh_execute(ssh, command, - check_exit_code=check_exit) - except Exception as e: - LOG.error(e) - greenthread.sleep(randint(20, 500) / 100.0) - msg = (_("SSH Command failed after '%(total_attempts)r' " - "attempts : '%(command)s'") % - {'total_attempts': total_attempts, 'command': command}) - LOG.error(msg) - raise exception.CinderException(message=msg) - except Exception: - with excutils.save_and_reraise_exception(): - LOG.error(_("Error running ssh command: %s") % command) - def _delete_3par_host(self, hostname): self.client.deleteHost(hostname) @@ -568,41 +475,24 @@ exit def _set_qos_rule(self, qos, vvs_name): max_io = self._get_qos_value(qos, 'maxIOPS') max_bw = self._get_qos_value(qos, 'maxBWS') - cmd = ['setqos'] - if max_io is not None: - cmd.extend(['-io', '%s' % max_io]) - if max_bw is not None: - cmd.extend(['-bw', '%sM' % max_bw]) - cmd.append('vvset:' + vvs_name) - self._cli_run(cmd) + self.client.setQOSRule(vvs_name, max_io, max_bw) def _add_volume_to_volume_set(self, volume, volume_name, cpg, vvs_name, qos): if vvs_name is not None: # Admin has set a volume set name to add the volume to - out = self._cli_run(['createvvset', '-add', vvs_name, volume_name]) - if out and len(out) == 1: - if 'does not exist' in out[0]: - msg = _('VV Set %s does not exist.') % vvs_name - LOG.error(msg) - raise exception.InvalidInput(reason=msg) + try: + self.client.addVolumeToVolumeSet(vvs_name, volume_name) + except hpexceptions.HTTPNotFound: + msg = _('VV Set %s does not exist.') % vvs_name + LOG.error(msg) + raise exception.InvalidInput(reason=msg) else: vvs_name = self._get_3par_vvs_name(volume['id']) domain = self.get_domain(cpg) - if domain is not None: - self._cli_run(['createvvset', '-domain', domain, vvs_name]) - else: - self._cli_run(['createvvset', vvs_name]) + self.client.createVolumeSet(vvs_name, domain) self._set_qos_rule(qos, vvs_name) - self._cli_run(['createvvset', '-add', vvs_name, volume_name]) - - def _remove_volume_set(self, vvs_name): - # Must first clear the QoS rules before removing the volume set - self._cli_run(['setqos', '-clear', 'vvset:%s' % (vvs_name)]) - self._cli_run(['removevvset', '-f', vvs_name]) - - def _remove_volume_from_volume_set(self, volume_name, vvs_name): - self._cli_run(['removevvset', '-f', vvs_name, volume_name]) + self.client.addVolumeToVolumeSet(vvs_name, volume_name) def get_cpg(self, volume, allowSnap=False): volume_name = self._get_3par_vol_name(volume['id']) @@ -768,16 +658,9 @@ exit def _copy_volume(self, src_name, dest_name, cpg=None, snap_cpg=None, tpvv=True): # Virtual volume sets are not supported with the -online option - cmd = ['createvvcopy', '-p', src_name, '-online'] - if snap_cpg: - cmd.extend(['-snp_cpg', snap_cpg]) - if tpvv: - cmd.append('-tpvv') - if cpg: - cmd.append(cpg) - cmd.append(dest_name) - LOG.debug('Creating clone of a volume with %s' % cmd) - self._cli_run(cmd) + LOG.debug('Creating clone of a volume %s' % src_name) + self.client.copyVolume(src_name, dest_name, cpg, + snap_cpg, tpvv) def get_next_word(self, s, search_string): """Return the next word. @@ -815,26 +698,6 @@ exit LOG.error(str(ex)) raise exception.CinderException(ex) - def _get_vvset_from_3par(self, volume_name): - """Get Virtual Volume Set from 3PAR. - - The only way to do this currently is to try and delete the volume - to get the error message. - - NOTE(walter-boring): don't call this unless you know the volume is - already in a vvset! - """ - cmd = ['removevv', '-f', volume_name] - LOG.debug("Issuing remove command to find vvset name %s" % cmd) - out = self._cli_run(cmd) - vvset_name = None - if out and len(out) > 1: - if out[1].startswith("Attempt to delete "): - words = out[1].split(" ") - vvset_name = words[len(words) - 1] - - return vvset_name - def delete_volume(self, volume): try: volume_name = self._get_3par_vol_name(volume['id']) @@ -847,19 +710,19 @@ exit if ex.get_code() == 34: # This is a special case which means the # volume is part of a volume set. - vvset_name = self._get_vvset_from_3par(volume_name) + vvset_name = self.client.findVolumeSet(volume_name) LOG.debug("Returned vvset_name = %s" % vvset_name) if vvset_name is not None and \ vvset_name.startswith('vvs-'): # We have a single volume per volume set, so # remove the volume set. - self._remove_volume_set( + self.client.deleteVolumeSet( self._get_3par_vvs_name(volume['id'])) elif vvset_name is not None: # We have a pre-defined volume set just remove the # volume and leave the volume set. - self._remove_volume_from_volume_set(volume_name, - vvset_name) + self.client.removeVolumeFromVolumeSet(vvset_name, + volume_name) self.client.deleteVolume(volume_name) else: LOG.error(str(ex)) @@ -998,8 +861,7 @@ exit volume_name = self._get_3par_vol_name(volume['id']) if value is None: value = '' - cmd = ['setvv', '-setkv', key + '=' + value, volume_name] - self._cli_run(cmd) + self.client.setVolumeMetaData(volume_name, key, value) except Exception as ex: msg = _('Failure in update_volume_key_value_pair:%s') % str(ex) LOG.error(msg) @@ -1013,8 +875,7 @@ exit self._get_3par_vol_name(volume['id']), str(key))) try: volume_name = self._get_3par_vol_name(volume['id']) - cmd = ['setvv', '-clrkey', key, volume_name] - self._cli_run(cmd) + self.client.removeVolumeMetaData(volume_name, key) except Exception as ex: msg = _('Failure in clear_volume_key_value_pair:%s') % str(ex) LOG.error(msg) diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index 683f75d5e4d..4e09d5369b6 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -54,10 +54,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): 1.2.2 - Added try/finally around client login/logout. 1.2.3 - Added ability to add WWNs to host. 1.2.4 - Added metadata during attach/detach bug #1258033. + 1.3.0 - Removed all SSH code. We rely on the hp3parclient now. """ - VERSION = "1.2.4" + VERSION = "1.3.0" def __init__(self, *args, **kwargs): super(HP3PARFCDriver, self).__init__(*args, **kwargs) @@ -232,19 +233,22 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): the same wwn but with a different hostname, return the hostname used by 3PAR. """ - if domain is not None: - command = ['createhost', '-persona', persona_id, '-domain', domain, - hostname] - else: - command = ['createhost', '-persona', persona_id, hostname] + # first search for an existing host + host_found = None for wwn in wwns: - command.append(wwn) + host_found = self.common.client.findHost(wwn=wwn) + if host_found is not None: + break - out = self.common._cli_run(command) - if out and len(out) > 1: - return self.common.parse_create_host_error(hostname, out) - - return hostname + if host_found is not None: + self.common.hosts_naming_dict[hostname] = host_found + return host_found + else: + persona_id = int(persona_id) + self.common.client.createHost(hostname, FCWwns=wwns, + optional={'domain': domain, + 'persona': persona_id}) + return hostname def _modify_3par_fibrechan_host(self, hostname, wwn): mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD, diff --git a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py index b78b6601f2c..b5c331d3128 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py +++ b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py @@ -58,10 +58,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 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 + 1.3.0 - Removed all SSH code. We rely on the hp3parclient now. """ - VERSION = "1.2.6" + VERSION = "1.3.0" def __init__(self, *args, **kwargs): super(HP3PARISCSIDriver, self).__init__(*args, **kwargs) @@ -302,16 +303,21 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): the same iqn but with a different hostname, return the hostname used by 3PAR. """ - if domain is not None: - cmd = ['createhost', '-iscsi', '-persona', persona_id, '-domain', - domain, hostname, iscsi_iqn] + # first search for an existing host + host_found = self.common.client.findHost(iqn=iscsi_iqn) + if host_found is not None: + self.common.hosts_naming_dict[hostname] = host_found + return host_found else: - cmd = ['createhost', '-iscsi', '-persona', persona_id, hostname, - iscsi_iqn] - out = self.common._cli_run(cmd) - if out and len(out) > 1: - return self.common.parse_create_host_error(hostname, out) - return hostname + if isinstance(iscsi_iqn, str) or isinstance(iscsi_iqn, unicode): + iqn = [iscsi_iqn] + else: + iqn = iscsi_iqn + persona_id = int(persona_id) + self.common.client.createHost(hostname, iscsiNames=iqn, + optional={'domain': domain, + 'persona': persona_id}) + return hostname def _modify_3par_iscsi_host(self, hostname, iscsi_iqn): mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD,