Merge "Critical fix for MSA 2060 and MSA 1060"
This commit is contained in:
commit
6ad1ab0c72
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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,7 +434,11 @@ 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:
|
||||||
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:
|
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
|
||||||
if '(-10058)' in e.msg:
|
if '(-10058)' in e.msg:
|
||||||
|
@ -434,11 +452,18 @@ class STXClient(object):
|
||||||
|
|
||||||
while lun < 255:
|
while lun < 255:
|
||||||
try:
|
try:
|
||||||
self._request("/map/volume",
|
if self.is_g5_fw():
|
||||||
volume_name,
|
self._request("/map/volume",
|
||||||
lun=str(lun),
|
volume_name,
|
||||||
host=host,
|
lun=str(lun),
|
||||||
access="rw")
|
initiator=host,
|
||||||
|
access="rw")
|
||||||
|
else:
|
||||||
|
self._request("/map/volume",
|
||||||
|
volume_name,
|
||||||
|
lun=str(lun),
|
||||||
|
host=host,
|
||||||
|
access="rw")
|
||||||
return lun
|
return lun
|
||||||
except stx_exception.RequestError as e:
|
except stx_exception.RequestError as e:
|
||||||
# -3177 => "The specified LUN overlaps a previously defined LUN
|
# -3177 => "The specified LUN overlaps a previously defined LUN
|
||||||
|
@ -468,7 +493,10 @@ class STXClient(object):
|
||||||
else:
|
else:
|
||||||
host = connector['initiator']
|
host = connector['initiator']
|
||||||
try:
|
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:
|
except stx_exception.RequestError as e:
|
||||||
# -10050 => The volume was not found on this system.
|
# -10050 => The volume was not found on this system.
|
||||||
# This can occur during controller failover.
|
# This can occur during controller failover.
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue