Add @Redfish.Settings update status and expose it for BIOS

Adds logic to determine status exposed to user based on
message severities. This is initial logic and in future can
be improved to determine, e.g., if settings update is pending.

Change-Id: Ic5da181af128641c6eaa432be566cbe0a5f69de4
Story: 2001791
Task: 19767
This commit is contained in:
Aija Jaunteva 2018-08-14 14:39:28 +03:00
parent 86ffbcb6ff
commit 51032b62ca
10 changed files with 164 additions and 10 deletions

View File

@ -0,0 +1,5 @@
---
features:
- |
``Bios`` resource introduces ``update_status`` property that exposes
the status and any errors of last BIOS attribute update.

View File

@ -180,7 +180,8 @@ class Sushy(base.ResourceBase):
:returns: The System object
"""
return system.System(self._conn, identity,
redfish_version=self.redfish_version)
redfish_version=self.redfish_version,
registries=self._get_message_registries())
def get_chassis_collection(self):
"""Get the ChassisCollection object

View File

@ -18,7 +18,44 @@ import logging
from sushy.resources import base
from sushy.resources import common
from sushy.resources import constants as res_cons
from sushy.resources import mappings as res_maps
from sushy.resources.registry import message_registry
# Settings update statuses
UPDATE_UNKNOWN = 0
"""Update status unknown"""
UPDATE_SUCCESS = 1
"""Update was successful"""
UPDATE_FAILURE = 2
"""Update encountered errors"""
UPDATE_PENDING = 3
"""Update waiting for being applied"""
NO_UPDATES = 4
"""No updates made"""
class SettingsUpdate(object):
"""Contains Settings update status and details of the update"""
def __init__(self, status, messages):
self._status = status
self._messages = messages
@property
def status(self):
"""The status of the update"""
return self._status
@property
def messages(self):
"""List of :class:`.MessageListField` with messages from the update"""
return self._messages
LOG = logging.getLogger(__name__)
@ -161,3 +198,29 @@ class SettingsField(base.CompositeField):
@property
def resource_uri(self):
return self._settings_object_idref.resource_uri
def get_status(self, registries):
"""Determines the status of last update based
Uses message id-s and severity to determine the status.
:param registries: registries to use to parse message
:returns: :class:`.SettingsUpdate` object containing status
and any messages
"""
if not self.time:
return SettingsUpdate(NO_UPDATES, None)
parsed_msgs = []
for m in self.messages:
parsed_msgs.append(
message_registry.parse_message(registries, m))
any_errors = any(m for m in parsed_msgs
if not m.severity == res_cons.SEVERITY_OK)
if any_errors:
status = UPDATE_FAILURE
else:
status = UPDATE_SUCCESS
return SettingsUpdate(status, parsed_msgs)

View File

@ -31,6 +31,17 @@ class ActionsField(base.CompositeField):
class Bios(base.ResourceBase):
def __init__(self, connector, path, registries, *args, **kwargs):
super(Bios, self).__init__(connector, path, *args, **kwargs)
self._registries = registries
"""A class representing a Bios
:param connector: A Connector instance
:param path: Sub-URI path to the Bios resource
:param registries: Dict of message registries to be used when
parsing messages of attribute update status
"""
identity = base.Field('Id', required=True)
"""The Bios resource identity string"""
@ -65,6 +76,7 @@ class Bios(base.ResourceBase):
"""Pending BIOS settings resource"""
return Bios(
self._conn, self._settings.resource_uri,
registries=None,
redfish_version=self.redfish_version)
@property
@ -153,3 +165,12 @@ class Bios(base.ResourceBase):
'OldPassword': old_password,
'PasswordName': password_name})
LOG.info('BIOS password %s is being changed', self.identity)
@property
def update_status(self):
"""Status of the last attribute update
:returns: :class:`sushy.resources.settings.SettingsUpdate` object
containing status and any messages
"""
return self._settings.get_status(self._registries)

View File

@ -135,15 +135,19 @@ class System(base.ResourceBase):
_actions = ActionsField('Actions', required=True)
def __init__(self, connector, identity, redfish_version=None):
def __init__(self, connector, identity, redfish_version=None,
registries=None):
"""A class representing a ComputerSystem
:param connector: A Connector instance
:param identity: The identity of the System resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of the given version.
:param registries: Dict of registries to be used in any resource
that needs registries to parse messages
"""
super(System, self).__init__(connector, identity, redfish_version)
self._registries = registries
def _get_reset_action_element(self):
reset_action = self._actions.reset
@ -315,7 +319,8 @@ class System(base.ResourceBase):
return bios.Bios(
self._conn,
utils.get_sub_resource_path_by(self, 'Bios'),
redfish_version=self.redfish_version)
redfish_version=self.redfish_version,
registries=self._registries)
@property
@utils.cache_it

View File

@ -20,9 +20,12 @@
"ETag": "9234ac83b9700123cc32",
"Messages": [
{
"MessageId": "Base.1.0.SettingsFailed",
"MessageId": "Test.1.0.Failed",
"RelatedProperties": [
"#/Attributes/ProcTurboMode"
],
"MessageArgs": [
"arg1"
]
}
],

View File

@ -3,8 +3,8 @@
"@odata.type": "#Settings.v1_2_0.Settings",
"ETag": "9234ac83b9700123cc32",
"Messages": [{
"MessageId": "Base.1.0.SettingsFailed",
"Message": "Settings update failed due to invalid value",
"MessageId": "Test.1.0.Failed",
"Message": "Settings %1 update failed due to invalid value",
"Severity": "Critical",
"Resolution": "Fix the value and try again",
"MessageArgs": [

View File

@ -18,6 +18,8 @@ import mock
from dateutil import parser
from sushy import exceptions
from sushy.resources.registry import message_registry
from sushy.resources import settings
from sushy.resources.system import bios
from sushy.tests.unit import base
@ -37,8 +39,16 @@ class BiosTestCase(base.TestCase):
bios_settings_json,
bios_settings_json]
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')
self.sys_bios = bios.Bios(
self.conn, '/redfish/v1/Systems/437XR1138R2/BIOS',
registries={'Test.1.0': registry},
redfish_version='1.0.2')
def test__parse_attributes(self):
@ -59,6 +69,8 @@ class BiosTestCase(base.TestCase):
self.sys_bios._settings._etag)
self.assertEqual('(404) 555-1212',
self.sys_bios.pending_attributes['AdminPhone'])
self.assertEqual(settings.UPDATE_FAILURE,
self.sys_bios.update_status.status)
def test_set_attribute(self):
self.sys_bios.set_attribute('ProcTurboMode', 'Disabled')

View File

@ -17,6 +17,7 @@ import json
import mock
from sushy.resources import constants as res_cons
from sushy.resources.registry import message_registry
from sushy.resources import settings
from sushy.tests.unit import base
@ -30,6 +31,14 @@ class SettingsFieldTestCase(base.TestCase):
self.settings = settings.SettingsField()
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')
self.registries = {'Test.1.0': registry}
@mock.patch.object(settings, 'LOG', autospec=True)
def test__load(self, mock_LOG):
instance = self.settings._load(self.json, mock.Mock())
@ -40,9 +49,9 @@ class SettingsFieldTestCase(base.TestCase):
instance.time)
self.assertEqual('/redfish/v1/Systems/437XR1138R2/BIOS/Settings',
instance._settings_object_idref.resource_uri)
self.assertEqual('Base.1.0.SettingsFailed',
self.assertEqual('Test.1.0.Failed',
instance.messages[0].message_id)
self.assertEqual('Settings update failed due to invalid value',
self.assertEqual('Settings %1 update failed due to invalid value',
instance.messages[0].message)
self.assertEqual(res_cons.SEVERITY_CRITICAL,
instance.messages[0].severity)
@ -67,3 +76,33 @@ class SettingsFieldTestCase(base.TestCase):
conn.patch.assert_called_once_with(
'/redfish/v1/Systems/437XR1138R2/BIOS/Settings',
data={'Attributes': {'key': 'value'}})
def test_get_status_failure(self):
instance = self.settings._load(self.json, mock.Mock())
status = instance.get_status(self.registries)
self.assertEqual(status.status,
settings.UPDATE_FAILURE)
self.assertEqual(status.messages[0].severity,
res_cons.SEVERITY_CRITICAL)
self.assertEqual(status.messages[0].message,
'The property arg1 broke everything.')
def test_get_status_success(self):
instance = self.settings._load(self.json, mock.Mock())
instance.messages[0].message_id = 'Test.1.0.Success'
instance.messages[0].severity = res_cons.SEVERITY_OK
status = instance.get_status(self.registries)
self.assertEqual(status.status,
settings.UPDATE_SUCCESS)
self.assertEqual(status.messages[0].severity, res_cons.SEVERITY_OK)
self.assertEqual(status.messages[0].message,
'Everything done successfully.')
def test_get_status_noupdates(self):
instance = self.settings._load(self.json, mock.Mock())
instance.time = None
status = instance.get_status(self.registries)
self.assertEqual(status.status,
settings.NO_UPDATES)
self.assertIsNone(status.messages)

View File

@ -100,11 +100,16 @@ class MainTestCase(base.TestCase):
redfish_version=self.root.redfish_version)
@mock.patch.object(system, 'System', autospec=True)
def test_get_system(self, mock_system):
@mock.patch('sushy.Sushy._get_message_registries', autospec=True)
def test_get_system(self, mock_registries, mock_system):
mock_registry = mock.Mock()
mock_registries.return_value = [mock_registry]
self.root._standard_message_registries_path = None
self.root.get_system('fake-system-id')
mock_system.assert_called_once_with(
self.root._conn, 'fake-system-id',
redfish_version=self.root.redfish_version)
redfish_version=self.root.redfish_version,
registries=[mock_registry])
@mock.patch.object(chassis, 'Chassis', autospec=True)
def test_get_chassis(self, mock_chassis):