commit 0d554e06ca5a09ecc7c826721e820289a248b9ff Author: Chris M Date: Wed Sep 30 02:04:01 2020 +0000 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 (cherry picked from commit d739b867144f5b5069ad7ef357eb74ccb02149ad) diff --git a/cinder/tests/unit/volume/drivers/test_seagate.py b/cinder/tests/unit/volume/drivers/test_seagate.py index d7a331a..472121d 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 dbc7cc0..6a10a62 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 1961d2f..f014915 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 0000000..3c7e3d8 --- /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.