From d739b867144f5b5069ad7ef357eb74ccb02149ad Mon Sep 17 00:00:00 2001 From: Chris M Date: Wed, 30 Sep 2020 02:04:01 +0000 Subject: [PATCH] Critical fix for MSA 2060 and MSA 1060 Correct omission in HPE MSA driver doc and fix driver failures caused by use of deprecated API command syntax that's not accepted by the latest firmware. The changes are conditional on the firmware version so that arrays with older firmware will not be affected. Change-Id: I73b093bcee4ac83cb80480097818b28104f8e15f Closes-Bug: #1897926 --- .../tests/unit/volume/drivers/test_seagate.py | 17 +++- cinder/volume/drivers/stx/client.py | 79 +++++++++++++++---- .../block-storage/drivers/hp-msa-driver.rst | 6 +- .../notes/msa2060-99150398a9c416f6.yaml | 7 ++ 4 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/msa2060-99150398a9c416f6.yaml diff --git a/cinder/tests/unit/volume/drivers/test_seagate.py b/cinder/tests/unit/volume/drivers/test_seagate.py index d7a331afc85..472121d64ee 100644 --- a/cinder/tests/unit/volume/drivers/test_seagate.py +++ b/cinder/tests/unit/volume/drivers/test_seagate.py @@ -50,6 +50,9 @@ resp_fw_ti = '''T252R07 resp_fw = '''GLS220R001 0''' +resp_fw_nomatch = '''Z + 0''' + resp_system = ''' 00C0FFEEEEEE 0 @@ -186,6 +189,12 @@ class TestSeagateClient(test.TestCase): self.assertRaises(stx_exception.AuthenticationError, self.client.login) + m.text.encode.side_effect = [resp_login, resp_fw_nomatch, resp_system] + self.client.login() + self.assertEqual('Z', self.client._fw_type) + self.assertEqual(0, self.client._fw_rev) + self.assertEqual(False, self.client.is_g5_fw()) + m.text.encode.side_effect = [resp_login, resp_fw, resp_system] self.client.login() self.assertEqual(session_key, self.client._session_key) @@ -313,11 +322,15 @@ class TestSeagateClient(test.TestCase): @mock.patch.object(STXClient, '_request') def test_list_luns_for_host(self, mock_request): mock_request.side_effect = [etree.XML(response_no_lun), + etree.XML(response_lun), etree.XML(response_lun)] - self.client._fw = 'T100' + self.client._fw_type = 'T' self.client.list_luns_for_host('dummy') mock_request.assert_called_with('/show/host-maps', 'dummy') - self.client._fw = 'G221' + self.client._fw_type = 'G' + self.client.list_luns_for_host('dummy') + mock_request.assert_called_with('/show/maps/initiator', 'dummy') + self.client._fw_type = 'I' self.client.list_luns_for_host('dummy') mock_request.assert_called_with('/show/maps/initiator', 'dummy') diff --git a/cinder/volume/drivers/stx/client.py b/cinder/volume/drivers/stx/client.py index dbc7cc078f1..6a10a62f992 100644 --- a/cinder/volume/drivers/stx/client.py +++ b/cinder/volume/drivers/stx/client.py @@ -17,6 +17,7 @@ import hashlib import math +import re import time from lxml import etree @@ -44,7 +45,8 @@ class STXClient(object): self._session_key = None self.ssl_verify = ssl_verify self._set_host(self._mgmt_ip_addrs[0]) - self._fw = '' + self._fw_type = '' + self._fw_rev = 0 self._driver_name = self.__class__.__name__.split('.')[0] self._array_name = 'unknown' self._luns_in_use_by_host = {} @@ -245,8 +247,19 @@ class STXClient(object): return False def is_titanium(self): - """True if array is an older generation.""" - return True if len(self._fw) > 0 and self._fw[0] == 'T' else False + """True for older array firmware.""" + return self._fw_type == 'T' + + def is_g5_fw(self): + """Identify firmware updated in/after 2020. + + Long-deprecated commands have or will be removed. + """ + if self._fw_type in ['I', 'V']: + return True + if self._fw_type == 'G' and self._fw_rev >= 280: + return True + return False def create_volume(self, name, size, backend_name, backend_type): # NOTE: size is in this format: [0-9]+GiB @@ -390,7 +403,8 @@ class STXClient(object): if not isinstance(ids, list): ids = [ids] try: - xml = self._request('/show/volume-maps', volume_name) + cmd = "/show/volume-maps" if self.is_titanium() else "/show/maps" + xml = self._request(cmd, volume_name) for obj in xml.xpath("//OBJECT[@basetype='volume-view-mappings']"): lun = obj.findtext("PROPERTY[@name='lun']") @@ -420,7 +434,11 @@ class STXClient(object): if host_status != 0: hostname = self._safe_hostname(connector['host']) try: - self._request("/create/host", hostname, id=host) + if self.is_g5_fw(): + self._request("/set/initiator", nickname=hostname, + id=host) + else: + self._request("/create/host", hostname, id=host) except stx_exception.RequestError as e: # -10058: The host identifier or nickname is already in use if '(-10058)' in e.msg: @@ -434,11 +452,18 @@ class STXClient(object): while lun < 255: try: - self._request("/map/volume", - volume_name, - lun=str(lun), - host=host, - access="rw") + if self.is_g5_fw(): + self._request("/map/volume", + volume_name, + lun=str(lun), + initiator=host, + access="rw") + else: + self._request("/map/volume", + volume_name, + lun=str(lun), + host=host, + access="rw") return lun except stx_exception.RequestError as e: # -3177 => "The specified LUN overlaps a previously defined LUN @@ -468,7 +493,10 @@ class STXClient(object): else: host = connector['initiator'] try: - self._request("/unmap/volume", volume_name, host=host) + if self.is_g5_fw(): + self._request("/unmap/volume", volume_name, initiator=host) + else: + self._request("/unmap/volume", volume_name, host=host) except stx_exception.RequestError as e: # -10050 => The volume was not found on this system. # This can occur during controller failover. @@ -574,12 +602,20 @@ class STXClient(object): return True def _check_host(self, host): - host_status = -1 + """Return 0 if initiator id found in the array's host table.""" + if self.is_g5_fw(): + tree = self._request("/show/initiators") + for prop in tree.xpath("//PROPERTY[@name='id' and text()='%s']" + % host): + return 0 + return -1 + + # Use older syntax for older firmware tree = self._request("/show/hosts") for prop in tree.xpath("//PROPERTY[@name='host-id' and text()='%s']" % host): - host_status = 0 - return host_status + return 0 + return -1 def _safe_hostname(self, hostname): """Modify an initiator name to match firmware requirements. @@ -650,7 +686,16 @@ class STXClient(object): return self._get_size(size) def get_firmware_version(self): + """Get the array firmware version""" tree = self._request("/show/controllers") - self._fw = tree.xpath("//PROPERTY[@name='sc-fw']")[0].text - LOG.debug("Array firmware is %s\n", self._fw) - return self._fw + s = tree.xpath("//PROPERTY[@name='sc-fw']")[0].text + if len(s): + self._fw_type = s[0] + fw_rev_match = re.match('^[^0-9]*([0-9]+).*', s) + if not fw_rev_match: + LOG.error('firmware revision not found in "%s"', s) + return s + self._fw_rev = int(fw_rev_match.groups()[0]) + LOG.debug("Array firmware is %s (%s%d)\n", + s, self._fw_type, self._fw_rev) + return s diff --git a/doc/source/configuration/block-storage/drivers/hp-msa-driver.rst b/doc/source/configuration/block-storage/drivers/hp-msa-driver.rst index 1961d2fb31c..f0149153d9c 100644 --- a/doc/source/configuration/block-storage/drivers/hp-msa-driver.rst +++ b/doc/source/configuration/block-storage/drivers/hp-msa-driver.rst @@ -2,9 +2,9 @@ HPE MSA Fibre Channel and iSCSI drivers ======================================= -The ``HPMSAFCDriver`` and ``HPMSAISCSIDriver`` Cinder drivers allow the -HPE MSA 2050, 1050, 2040, and 1040 arrays to be used for Block Storage in -OpenStack deployments. +The ``HPMSAFCDriver`` and ``HPMSAISCSIDriver`` Cinder drivers allow +the HPE MSA 2060, 1060, 2050, 1050, 2040, and 1040 arrays to be used +for Block Storage in OpenStack deployments. System requirements ~~~~~~~~~~~~~~~~~~~ diff --git a/releasenotes/notes/msa2060-99150398a9c416f6.yaml b/releasenotes/notes/msa2060-99150398a9c416f6.yaml new file mode 100644 index 00000000000..3c7e3d8ed03 --- /dev/null +++ b/releasenotes/notes/msa2060-99150398a9c416f6.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + HPMSA driver: The HPE MSA driver was updated to avoid using + deprecated command syntax that has been removed in the latest + version of the MSA API. This is required to support the newest + firmware in the MSA 2060/1060.