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
This commit is contained in:
Chris M 2020-09-30 02:04:01 +00:00
parent c8ce118f0a
commit d739b86714
4 changed files with 87 additions and 22 deletions

View File

@ -50,6 +50,9 @@ resp_fw_ti = '''<RESPONSE><PROPERTY name="sc-fw">T252R07</PROPERTY>
resp_fw = '''<RESPONSE><PROPERTY name="sc-fw">GLS220R001</PROPERTY> resp_fw = '''<RESPONSE><PROPERTY name="sc-fw">GLS220R001</PROPERTY>
<PROPERTY name="return-code">0</PROPERTY></RESPONSE>''' <PROPERTY name="return-code">0</PROPERTY></RESPONSE>'''
resp_fw_nomatch = '''<RESPONSE><PROPERTY name="sc-fw">Z</PROPERTY>
<PROPERTY name="return-code">0</PROPERTY></RESPONSE>'''
resp_system = '''<RESPONSE> resp_system = '''<RESPONSE>
<PROPERTY name="midplane-serial-number">00C0FFEEEEEE</PROPERTY> <PROPERTY name="midplane-serial-number">00C0FFEEEEEE</PROPERTY>
<PROPERTY name="return-code">0</PROPERTY> <PROPERTY name="return-code">0</PROPERTY>
@ -186,6 +189,12 @@ class TestSeagateClient(test.TestCase):
self.assertRaises(stx_exception.AuthenticationError, self.assertRaises(stx_exception.AuthenticationError,
self.client.login) 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] m.text.encode.side_effect = [resp_login, resp_fw, resp_system]
self.client.login() self.client.login()
self.assertEqual(session_key, self.client._session_key) self.assertEqual(session_key, self.client._session_key)
@ -313,11 +322,15 @@ class TestSeagateClient(test.TestCase):
@mock.patch.object(STXClient, '_request') @mock.patch.object(STXClient, '_request')
def test_list_luns_for_host(self, mock_request): def test_list_luns_for_host(self, mock_request):
mock_request.side_effect = [etree.XML(response_no_lun), mock_request.side_effect = [etree.XML(response_no_lun),
etree.XML(response_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') self.client.list_luns_for_host('dummy')
mock_request.assert_called_with('/show/host-maps', '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') self.client.list_luns_for_host('dummy')
mock_request.assert_called_with('/show/maps/initiator', 'dummy') mock_request.assert_called_with('/show/maps/initiator', 'dummy')

View File

@ -17,6 +17,7 @@
import hashlib import hashlib
import math import math
import re
import time import time
from lxml import etree from lxml import etree
@ -44,7 +45,8 @@ class STXClient(object):
self._session_key = None self._session_key = None
self.ssl_verify = ssl_verify self.ssl_verify = ssl_verify
self._set_host(self._mgmt_ip_addrs[0]) 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._driver_name = self.__class__.__name__.split('.')[0]
self._array_name = 'unknown' self._array_name = 'unknown'
self._luns_in_use_by_host = {} self._luns_in_use_by_host = {}
@ -245,8 +247,19 @@ class STXClient(object):
return False return False
def is_titanium(self): def is_titanium(self):
"""True if array is an older generation.""" """True for older array firmware."""
return True if len(self._fw) > 0 and self._fw[0] == 'T' else False 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): def create_volume(self, name, size, backend_name, backend_type):
# NOTE: size is in this format: [0-9]+GiB # NOTE: size is in this format: [0-9]+GiB
@ -390,7 +403,8 @@ class STXClient(object):
if not isinstance(ids, list): if not isinstance(ids, list):
ids = [ids] ids = [ids]
try: 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']"): for obj in xml.xpath("//OBJECT[@basetype='volume-view-mappings']"):
lun = obj.findtext("PROPERTY[@name='lun']") lun = obj.findtext("PROPERTY[@name='lun']")
@ -420,6 +434,10 @@ class STXClient(object):
if host_status != 0: if host_status != 0:
hostname = self._safe_hostname(connector['host']) hostname = self._safe_hostname(connector['host'])
try: try:
if self.is_g5_fw():
self._request("/set/initiator", nickname=hostname,
id=host)
else:
self._request("/create/host", hostname, id=host) self._request("/create/host", hostname, id=host)
except stx_exception.RequestError as e: except stx_exception.RequestError as e:
# -10058: The host identifier or nickname is already in use # -10058: The host identifier or nickname is already in use
@ -434,6 +452,13 @@ class STXClient(object):
while lun < 255: while lun < 255:
try: try:
if self.is_g5_fw():
self._request("/map/volume",
volume_name,
lun=str(lun),
initiator=host,
access="rw")
else:
self._request("/map/volume", self._request("/map/volume",
volume_name, volume_name,
lun=str(lun), lun=str(lun),
@ -468,6 +493,9 @@ class STXClient(object):
else: else:
host = connector['initiator'] host = connector['initiator']
try: try:
if self.is_g5_fw():
self._request("/unmap/volume", volume_name, initiator=host)
else:
self._request("/unmap/volume", volume_name, host=host) self._request("/unmap/volume", volume_name, host=host)
except stx_exception.RequestError as e: except stx_exception.RequestError as e:
# -10050 => The volume was not found on this system. # -10050 => The volume was not found on this system.
@ -574,12 +602,20 @@ class STXClient(object):
return True return True
def _check_host(self, host): 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") tree = self._request("/show/hosts")
for prop in tree.xpath("//PROPERTY[@name='host-id' and text()='%s']" for prop in tree.xpath("//PROPERTY[@name='host-id' and text()='%s']"
% host): % host):
host_status = 0 return 0
return host_status return -1
def _safe_hostname(self, hostname): def _safe_hostname(self, hostname):
"""Modify an initiator name to match firmware requirements. """Modify an initiator name to match firmware requirements.
@ -650,7 +686,16 @@ class STXClient(object):
return self._get_size(size) return self._get_size(size)
def get_firmware_version(self): def get_firmware_version(self):
"""Get the array firmware version"""
tree = self._request("/show/controllers") tree = self._request("/show/controllers")
self._fw = tree.xpath("//PROPERTY[@name='sc-fw']")[0].text s = tree.xpath("//PROPERTY[@name='sc-fw']")[0].text
LOG.debug("Array firmware is %s\n", self._fw) if len(s):
return self._fw 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

View File

@ -2,9 +2,9 @@
HPE MSA Fibre Channel and iSCSI drivers HPE MSA Fibre Channel and iSCSI drivers
======================================= =======================================
The ``HPMSAFCDriver`` and ``HPMSAISCSIDriver`` Cinder drivers allow the The ``HPMSAFCDriver`` and ``HPMSAISCSIDriver`` Cinder drivers allow
HPE MSA 2050, 1050, 2040, and 1040 arrays to be used for Block Storage in the HPE MSA 2060, 1060, 2050, 1050, 2040, and 1040 arrays to be used
OpenStack deployments. for Block Storage in OpenStack deployments.
System requirements System requirements
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -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.