From dfb1a090334d705425e739e8174a9c1c07009433 Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Mon, 10 Feb 2014 13:09:28 -0800 Subject: [PATCH] Update 3PAR drivers to pass cert test This patch updates both the HP 3PAR Fibre Channel and HP 3PAR iSCSI block storage drivers so that they can pass the driver certification tests. We also updated the unit tests to use mock instead of mox. These versions of the drivers require the new 3.0.0 hp3parclient, which can be downloaded from the pypi repository: https://pypi.python.org/pypi/hp3parclient The new hp3parclient requires the 3.1.3 firmware on the HP 3PAR. The driver certification results: Related-Bug: #1278575 Related-Bug: #1278577 Closes-Bug: #1279137 Change-Id: I72e0a76c865e54c58e41cc4409555d9042e30267 DocImpact: Document new driver requirements. --- cinder/tests/test_hp3par.py | 111 +++++++++--------- .../volume/drivers/san/hp/hp_3par_common.py | 40 ++++++- cinder/volume/drivers/san/hp/hp_3par_fc.py | 15 ++- cinder/volume/drivers/san/hp/hp_3par_iscsi.py | 15 ++- test-requirements.txt | 2 +- 5 files changed, 110 insertions(+), 73 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 93915d26485..7d7484b261a 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -1,5 +1,5 @@ # -# (c) Copyright 2013-2014 Hewlett-Packard Development Company, L.P. +# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -40,7 +40,7 @@ HP3PAR_SAN_SSH_CON_TIMEOUT = 44 HP3PAR_SAN_SSH_PRIVATE = 'foobar' -class HP3PARBaseDriver(): +class HP3PARBaseDriver(object): VOLUME_ID = 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7' CLONE_ID = 'd03338a9-9115-48a3-8dfc-000000000000' @@ -281,7 +281,8 @@ class HP3PARBaseDriver(): self.VOLUME_3PAR_NAME, 'osv-0DM4qZEVSKON-AAAAAAAAA', HP3PAR_CPG, - HP3PAR_CPG_SNAP, True), + {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True, + 'online': True}), mock.call.logout()] mock_client.assert_has_calls(expected) @@ -543,33 +544,33 @@ class HP3PARBaseDriver(): # 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}}]} + '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) @@ -607,7 +608,6 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): 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() @@ -909,7 +909,6 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): 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(), @@ -1046,11 +1045,11 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): 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'}]}] + 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) @@ -1151,25 +1150,25 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): config.iscsi_ip_address = '10.10.10.10' 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}]}} + '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, diff --git a/cinder/volume/drivers/san/hp/hp_3par_common.py b/cinder/volume/drivers/san/hp/hp_3par_common.py index 04797038565..82f14939e50 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -16,7 +16,7 @@ """ Volume driver common utilities for HP 3PAR Storage array -The 3PAR drivers requires 3.1.2 MU3 firmware on the 3PAR array. +The 3PAR drivers requires 3.1.3 firmware on the 3PAR array. You will need to install the python hp3parclient. sudo pip install hp3parclient @@ -55,7 +55,7 @@ from cinder.volume import volume_types LOG = logging.getLogger(__name__) -MIN_CLIENT_VERSION = '2.9.0' +MIN_CLIENT_VERSION = '3.0.0' hp3par_opts = [ cfg.StrOpt('hp3par_api_url', @@ -110,10 +110,11 @@ class HP3PARCommon(object): 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. + 2.0.0 - Update hp3parclient API uses 3.0.x """ - VERSION = "1.3.0" + VERSION = "2.0.0" stats = {} @@ -206,7 +207,6 @@ class HP3PARCommon(object): try: # make sure the default CPG exists self.validate_cpg(self.config.hp3par_cpg) - self.client.setHighConnections() finally: self.client_logout() @@ -426,6 +426,7 @@ class HP3PARCommon(object): try: self._delete_3par_host(hostname) + self._remove_hosts_naming_dict_host(hostname) except hpexceptions.HTTPConflict as ex: # host will only be removed after all vluns # have been removed @@ -434,6 +435,15 @@ class HP3PARCommon(object): else: raise + def _remove_hosts_naming_dict_host(self, hostname): + items = self.hosts_naming_dict.items() + lkey = None + for key, value in items: + if value == hostname: + lkey = key + if lkey is not None: + del self.hosts_naming_dict[lkey] + def _get_volume_type(self, type_id): ctxt = context.get_admin_context() return volume_types.get_volume_type(ctxt, type_id) @@ -659,8 +669,11 @@ class HP3PARCommon(object): tpvv=True): # Virtual volume sets are not supported with the -online option LOG.debug('Creating clone of a volume %s' % src_name) - self.client.copyVolume(src_name, dest_name, cpg, - snap_cpg, tpvv) + optional = {'tpvv': tpvv, 'online': True} + if snap_cpg is not None: + optional['snapCPG'] = snap_cpg + + self.client.copyVolume(src_name, dest_name, cpg, optional) def get_next_word(self, s, search_string): """Return the next word. @@ -706,6 +719,21 @@ class HP3PARCommon(object): # volume set name in the error. try: self.client.deleteVolume(volume_name) + except hpexceptions.HTTPBadRequest as ex: + if ex.get_code() == 29: + if self.client.isOnlinePhysicalCopy(volume_name): + LOG.debug(_("Found an online copy for %(volume)s") + % {'volume': volume_name}) + # the volume is in process of being cloned. + # stopOnlinePhysicalCopy will also delete + # the volume once it stops the copy. + self.client.stopOnlinePhysicalCopy(volume_name) + else: + LOG.error(str(ex)) + raise ex + else: + LOG.error(str(ex)) + raise ex except hpexceptions.HTTPConflict as ex: if ex.get_code() == 34: # This is a special case which means the diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index 4e09d5369b6..b0ac4d066f0 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -17,11 +17,11 @@ # """ Volume driver for HP 3PAR Storage array. -This driver requires 3.1.2 MU2 firmware on the 3PAR array, using -the 2.x version of the hp3parclient. +This driver requires 3.1.3 firmware on the 3PAR array, using +the 3.x version of the hp3parclient. You will need to install the python hp3parclient. -sudo pip install --upgrade "hp3parclient>=2.0" +sudo pip install --upgrade "hp3parclient>=3.0" Set the following in the cinder.conf file to enable the 3PAR Fibre Channel Driver along with the required flags: @@ -55,10 +55,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): 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. + 2.0.0 - Update hp3parclient API uses 3.0.x """ - VERSION = "1.3.0" + VERSION = "2.0.0" def __init__(self, *args, **kwargs): super(HP3PARFCDriver, self).__init__(*args, **kwargs) @@ -319,7 +320,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): @utils.synchronized('3par', external=True) def extend_volume(self, volume, new_size): - self.common.extend_volume(volume, new_size) + self.common.client_login() + try: + self.common.extend_volume(volume, new_size) + finally: + self.common.client_logout() @utils.synchronized('3par', external=True) def attach_volume(self, context, volume, instance_uuid, host_name, diff --git a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py index b5c331d3128..498c987ff06 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py +++ b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py @@ -15,11 +15,11 @@ # """ Volume driver for HP 3PAR Storage array. -This driver requires 3.1.2 MU3 firmware on the 3PAR array, using -the 2.x version of the hp3parclient. +This driver requires 3.1.3 firmware on the 3PAR array, using +the 3.x version of the hp3parclient. You will need to install the python hp3parclient. -sudo pip install --upgrade "hp3parclient>=2.0" +sudo pip install --upgrade "hp3parclient>=3.0" Set the following in the cinder.conf file to enable the 3PAR iSCSI Driver along with the required flags: @@ -59,10 +59,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 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. + 2.0.0 - Update hp3parclient API uses 3.0.x """ - VERSION = "1.3.0" + VERSION = "2.0.0" def __init__(self, *args, **kwargs): super(HP3PARISCSIDriver, self).__init__(*args, **kwargs) @@ -431,7 +432,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): @utils.synchronized('3par', external=True) def extend_volume(self, volume, new_size): - self.common.extend_volume(volume, new_size) + self.common.client_login() + try: + self.common.extend_volume(volume, new_size) + finally: + self.common.client_logout() @utils.synchronized('3par', external=True) def attach_volume(self, context, volume, instance_uuid, host_name, diff --git a/test-requirements.txt b/test-requirements.txt index 15729379016..2f5f3e3db8d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ hacking>=0.8.0,<0.9 coverage>=3.6 discover fixtures>=0.3.14 -hp3parclient>=2.0,<3.0 +hp3parclient>=3.0,<4.0 hplefthandclient>=1.0.0,<2.0.0 mock>=1.0 mox>=0.5.3