Make message parsing more resilient

Firmware from some vendors only includes the MessageKey in the
MessageID. In this case, fall back to finding the MessageKey first in
the Messages MessageRegistryFile and then in the BaseMessages
MessageRegistryFile.

If the message can't be found, then set the parsed message to 'unknown'
instead of throwing an exception.

If there are not enough arguments to populate the message, then
populate the missing arguments with 'unknown'.

Change-Id: I045f82167a415c92d6b67532222aef89ab1d68ef
This commit is contained in:
Christopher Dearborn 2020-08-30 17:36:24 -04:00
parent b1bf433512
commit bad68e1e60
5 changed files with 191 additions and 11 deletions

View File

@ -0,0 +1,10 @@
---
fixes:
- |
Makes message parsing more resilient by handling the case where the message
ID only contains a message key and no registry name. In this case, fall
back to the ``Messages`` message registry file and then to the
``BaseMessages`` message registry file. If the message ID cannot be found,
then set the message to ``unknown``. When parsing messages, if not enough
arguments were supplied, then fill in the remaining arguments with
``unknown``.

View File

@ -459,9 +459,10 @@ class Sushy(base.ResourceBase):
Fetches all registries if any provided by Redfish service
and combines together with packaged standard registries.
:returns: dict of combined message registries where key is
Registry_name.Major_version.Minor_version and value is registry
itself.
:returns: dict of combined message registries keyed by both the
registry name (Registry_name.Major_version.Minor_version) and the
message registry file identity, with the value being the actual
registry itself.
"""
standard = self._get_standard_message_registry_collection()
@ -473,9 +474,13 @@ class Sushy(base.ResourceBase):
if registry_col:
provided = registry_col.get_members()
registries.update({r.registry: r.get_message_registry(
self._language,
self._public_connector) for r in provided})
for r in provided:
message_registry = r.get_message_registry(
self._language,
self._public_connector)
registries[r.registry] = message_registry
if r.identity not in registries:
registries[r.identity] = message_registry
return registries

View File

@ -13,11 +13,14 @@
# This is referred from Redfish standard schema.
# https://redfish.dmtf.org/schemas/v1/MessageRegistry.v1_1_1.json
import logging
from sushy.resources import base
from sushy.resources import constants as res_cons
from sushy.resources import mappings as res_maps
LOG = logging.getLogger(__name__)
class MessageDictionaryField(base.DictionaryField):
@ -92,13 +95,44 @@ def parse_message(message_registries, message_field):
:returns: parsed settings.MessageListField with missing attributes filled
"""
registry, msg_key = message_field.message_id.rsplit('.', 1)
reg_msg = None
if '.' in message_field.message_id:
registry, msg_key = message_field.message_id.rsplit('.', 1)
reg_msg = message_registries[registry].messages[msg_key]
if (registry in message_registries and msg_key
in message_registries[registry].messages):
reg_msg = message_registries[registry].messages[msg_key]
else:
# Some firmware only reports the MessageKey and no RegistryName.
# Fall back to the MessageRegistryFile with Id of Messages next, and
# BaseMessages as a last resort
registry = 'unknown'
msg_key = message_field.message_id
mrf_ids = ['Messages', 'BaseMessages']
for mrf_id in mrf_ids:
if (mrf_id in message_registries and msg_key in
message_registries[mrf_id].messages):
reg_msg = message_registries[mrf_id].messages[msg_key]
break
if not reg_msg:
LOG.warning(
'Unable to find message for registry %(registry), '
'message ID %(msg_key)', {
'registry': registry,
'msg_key': msg_key})
if message_field.message is None:
message_field.message = 'unknown'
return message_field
msg = reg_msg.message
for i in range(1, reg_msg.number_of_args + 1):
msg = msg.replace('%%%i' % i, str(message_field.message_args[i - 1]))
if i <= len(message_field.message_args):
msg = msg.replace('%%%i' % i,
str(message_field.message_args[i - 1]))
else:
msg = msg.replace('%%%i' % i, 'unknown')
message_field.message = msg
if not message_field.severity:

View File

@ -131,3 +131,130 @@ class MessageRegistryTestCase(base.TestCase):
self.assertEqual(res_cons.SEVERITY_OK, parsed_msg.severity)
self.assertEqual('Everything done successfully.',
parsed_msg.message)
def test_parse_message_bad_registry(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Test.1.0.0': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'BadRegistry.TooBig'
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual(message_field, parsed_msg)
def test_parse_message_bad_message_key_existing_message(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Test.1.0.0': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'Test.1.0.0.BadMessageKey'
message_field.message = 'Message'
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual(message_field.message, 'Message')
self.assertEqual(message_field.message, parsed_msg.message)
def test_parse_message_bad_message_key_no_existing_message(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Test.1.0.0': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'Test.1.0.0.BadMessageKey'
message_field.message = None
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual(message_field.message, 'unknown')
self.assertEqual(message_field.message, parsed_msg.message)
def test_parse_message_fallback_to_messages(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Messages': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'Success'
message_field.severity = res_cons.SEVERITY_OK
message_field.resolution = 'Do nothing'
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual('Do nothing', parsed_msg.resolution)
self.assertEqual(res_cons.SEVERITY_OK, parsed_msg.severity)
self.assertEqual('Everything done successfully.',
parsed_msg.message)
def test_parse_message_fallback_to_basemessages(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'BaseMessages': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'Success'
message_field.severity = res_cons.SEVERITY_OK
message_field.resolution = 'Do nothing'
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual('Do nothing', parsed_msg.resolution)
self.assertEqual(res_cons.SEVERITY_OK, parsed_msg.severity)
self.assertEqual('Everything done successfully.',
parsed_msg.message)
def test_parse_message_fallback_failed(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Test.1.0.0': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'BadMessageKey'
message_field.message = None
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual(message_field.message, 'unknown')
self.assertEqual(message_field.message, parsed_msg.message)
def test_parse_message_not_enough_args(self):
conn = mock.Mock()
with open('sushy/tests/unit/json_samples/message_registry.json') as f:
conn.get.return_value.json.return_value = json.load(f)
registry = message_registry.MessageRegistry(
conn, '/redfish/v1/Registries/Test',
redfish_version='1.0.2')
registries = {'Test.1.0.0': registry}
message_field = sushy_base.MessageListField('Foo')
message_field.message_id = 'Test.1.0.0.TooBig'
message_field.message_args = ['arg1']
message_field.severity = None
message_field.resolution = None
parsed_msg = message_registry.parse_message(registries, message_field)
self.assertEqual('Try again', parsed_msg.resolution)
self.assertEqual(res_cons.SEVERITY_WARNING, parsed_msg.severity)
self.assertEqual('Property\'s arg1 value cannot be greater than '
'unknown.', parsed_msg.message)

View File

@ -325,13 +325,15 @@ class MainTestCase(base.TestCase):
mock_msg_reg2.registry_prefix = 'RegistryB'
mock_msg_reg2.registry_version = '1.0.0'
mock_msg_reg_file = mock.Mock()
mock_msg_reg_file.identity = 'Messages'
mock_msg_reg_file.registry = 'RegistryB.1.0'
mock_msg_reg_file.get_message_registry.return_value = mock_msg_reg2
mock_col.return_value.get_members.return_value = [mock_msg_reg_file]
registries = self.root.registries
self.assertEqual({'RegistryA.2.0': mock_msg_reg1,
'RegistryB.1.0': mock_msg_reg2}, registries)
'RegistryB.1.0': mock_msg_reg2,
'Messages': mock_msg_reg2}, registries)
@mock.patch('sushy.Sushy._get_standard_message_registry_collection',
autospec=True)
@ -347,6 +349,7 @@ class MainTestCase(base.TestCase):
mock_msg_reg2.registry_prefix = 'RegistryB'
mock_msg_reg2.registry_version = '1.0.0'
mock_msg_reg_file = mock.Mock()
mock_msg_reg_file.identity = 'Messages'
mock_msg_reg_file.registry = 'RegistryB.1.0'
mock_msg_reg_file.get_message_registry.return_value = mock_msg_reg2
mock_col.return_value.get_members.return_value = [mock_msg_reg_file]
@ -363,7 +366,8 @@ class MainTestCase(base.TestCase):
expected = {
'RegistryA.2.0': mock_msg_reg1,
'RegistryB.1.0': mock_msg_reg2
'RegistryB.1.0': mock_msg_reg2,
'Messages': mock_msg_reg2
}
self.assertEqual(expected, registries)