diff --git a/redfish_protocol_validator/console_scripts.py b/redfish_protocol_validator/console_scripts.py index 5eab203..a74aae7 100644 --- a/redfish_protocol_validator/console_scripts.py +++ b/redfish_protocol_validator/console_scripts.py @@ -13,6 +13,7 @@ from urllib3.exceptions import InsecureRequestWarning from http.client import HTTPConnection +from redfish_protocol_validator import data_model from redfish_protocol_validator import protocol_details from redfish_protocol_validator import report from redfish_protocol_validator import resources @@ -33,6 +34,7 @@ def perform_tests(sut: SystemUnderTest): protocol_details.test_protocol_details(sut) service_requests.test_service_requests(sut) service_responses.test_service_responses(sut) + data_model.test_data_model(sut) service_details.test_service_details(sut) security_details.test_security_details(sut) diff --git a/redfish_protocol_validator/constants.py b/redfish_protocol_validator/constants.py index c159d5c..16206f3 100644 --- a/redfish_protocol_validator/constants.py +++ b/redfish_protocol_validator/constants.py @@ -25,6 +25,7 @@ class Result(NoValue): class ResourceType(NoValue): MANAGER_ACCOUNT = auto() ROLE = auto() + BIOS = auto() class RequestType(NoValue): @@ -385,6 +386,16 @@ class Assertion(NoValue): 'that contains an entry for the Service Root and each resource that ' 'is a direct child of the Service Root.' ) + # Data model assertions (prefix of "DATA_") + DATA_SETTINGS_RES_SETTINGS_ANNOTATION = ( + 'For resources that support a future intended state, the response ' + 'shall contain a property with the @Redfish.Settings payload ' + 'annotation.' + ) + DATA_SETTINGS_RES_DATA_TYPE = ( + 'The settings resource shall be of the same schema definition as the ' + 'active resource.' + ) # Service details assertions (prefix of "SERV_") SERV_EVENT_POST_RESP = ( 'If the [Event Service] subscription request succeeds, the service ' diff --git a/redfish_protocol_validator/data_model.py b/redfish_protocol_validator/data_model.py new file mode 100644 index 0000000..0435460 --- /dev/null +++ b/redfish_protocol_validator/data_model.py @@ -0,0 +1,111 @@ +# Copyright Notice: +# Copyright 2020-2023 DMTF. All rights reserved. +# License: BSD 3-Clause License. For full text see link: +# https://github.com/DMTF/Redfish-Protocol-Validator/blob/master/LICENSE.md + +import requests + +from redfish_protocol_validator import utils +from redfish_protocol_validator.constants import Assertion, RequestType, ResourceType, Result +from redfish_protocol_validator.system_under_test import SystemUnderTest + +def test_settings_resources(sut): + """Perform tests from the 'Settings resource' sub-section of the spec.""" + + # Find resources that have a high likelihood of containing a settings resource + responses = sut.get_responses_by_method('GET', resource_type=ResourceType.BIOS) + if len(responses) == 0: + # No resources found + msg = 'No Bios resource found to test assertion' + sut.log(Result.NOT_TESTED, '', '', '', + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, msg) + sut.log(Result.NOT_TESTED, '', '', '', + Assertion.DATA_SETTINGS_RES_DATA_TYPE, msg) + return + + # Check each resource for a settings resource + for uri, response in responses.items(): + if response.ok: + response_data = response.json() + check_settings_type = False + + # Try to get the settings resource URI + settings_uri = response_data.get('@Redfish.Settings', {}).get('SettingsObject', {}).get('@odata.id') + + if settings_uri is not None: + # Settings annotation found; pass + sut.log(Result.PASS, 'GET', response.status_code, uri, + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, + 'Test passed') + check_settings_type = True + settings_response = sut.session.get(sut.rhost + settings_uri) + else: + # Not found; probe for potential settings resource + for segment in ['/SD', '/Settings']: + settings_uri = uri + segment + settings_response = sut.session.get(sut.rhost + settings_uri) + if settings_response.ok: + check_settings_type = True + msg = ('%s does not contain a settings annotation, but ' + 'the settings resource %s is accessible' % + (uri, settings_uri)) + sut.log(Result.FAIL, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, msg) + break + if not settings_response.ok: + # No responses + msg = ('%s does not contain a settings resource' % uri) + sut.log(Result.NOT_TESTED, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, msg) + + # If there's a settings resource, compare the resource types + if check_settings_type: + settings_response_data = settings_response.json() + if settings_response.ok: + # Successful settings resource response; compare @odata.type + if settings_response_data.get('@odata.type') != response_data.get('@odata.type'): + msg = ('The settings resource contains @odata.type %s, ' + 'but the active resource contains @odata.type ' + '%s' % (settings_response_data.get('@odata.type'), + response_data.get('@odata.type'))) + sut.log(Result.FAIL, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, + msg) + else: + sut.log(Result.PASS, 'GET', response.status_code, uri, + Assertion.DATA_SETTINGS_RES_DATA_TYPE, + 'Test passed') + else: + # Could not get the settings resource + msg = ('%s request to %s failed with status %s;' + 'extended error %s' % + (settings_response.request.method, settings_uri, + settings_response.status_code, + utils.get_extended_error(settings_response))) + sut.log(Result.WARN, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_DATA_TYPE, msg) + else: + msg = ('%s does not contain a settings resource' % uri) + sut.log(Result.NOT_TESTED, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_DATA_TYPE, msg) + else: + msg = ('%s request to %s failed with status %s;' + 'extended error %s' % + (response.request.method, uri, response.status_code, + utils.get_extended_error(response))) + sut.log(Result.WARN, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_SETTINGS_ANNOTATION, msg) + sut.log(Result.WARN, response.request.method, + response.status_code, uri, + Assertion.DATA_SETTINGS_RES_DATA_TYPE, msg) + + +def test_data_model(sut: SystemUnderTest): + """Perform tests from the 'Data model' section of the spec.""" + test_settings_resources(sut) diff --git a/redfish_protocol_validator/report.py b/redfish_protocol_validator/report.py index 1e3a704..4a8e7e2 100644 --- a/redfish_protocol_validator/report.py +++ b/redfish_protocol_validator/report.py @@ -91,6 +91,7 @@ ('PROTO_', 'Protocol Details'), ('REQ_', 'Service Requests'), ('RESP_', 'Service Responses'), + ('DATA_', 'Data Model'), ('SERV_', 'Service Details'), ('SEC_', 'Security Details'), ] diff --git a/redfish_protocol_validator/resources.py b/redfish_protocol_validator/resources.py index aead6b3..eb279e4 100644 --- a/redfish_protocol_validator/resources.py +++ b/redfish_protocol_validator/resources.py @@ -104,18 +104,35 @@ def get_default_resources(sut: SystemUnderTest, uri='/redfish/v1/', sut.set_service_uuid(root.get('UUID')) sut.set_supported_query_params(root.get('ProtocolFeaturesSupported', {})) - for prop in ['Systems', 'Chassis']: - if prop in root: - uri = root[prop]['@odata.id'] - sut.set_nav_prop_uri(prop, uri) - r = sut.session.get(sut.rhost + uri) - yield {'uri': uri, 'response': r} - if r.ok: - data = r.json() - if 'Members' in data and len(data['Members']): - uri = data['Members'][0]['@odata.id'] - r = sut.session.get(sut.rhost + uri) - yield {'uri': uri, 'response': r} + if 'Chassis' in root: + uri = root['Chassis']['@odata.id'] + sut.set_nav_prop_uri('Chassis', uri) + r = sut.session.get(sut.rhost + uri) + yield {'uri': uri, 'response': r} + if r.ok: + data = r.json() + if 'Members' in data and len(data['Members']): + uri = data['Members'][0]['@odata.id'] + r = sut.session.get(sut.rhost + uri) + yield {'uri': uri, 'response': r} + + if 'Systems' in root: + uri = root['Systems']['@odata.id'] + sut.set_nav_prop_uri('Systems', uri) + r = sut.session.get(sut.rhost + uri) + yield {'uri': uri, 'response': r} + if r.ok: + data = r.json() + if 'Members' in data and len(data['Members']): + uri = data['Members'][0]['@odata.id'] + r = sut.session.get(sut.rhost + uri) + yield {'uri': uri, 'response': r} + if r.ok: + d = r.json() + if 'Bios' in d: + uri = d['Bios']['@odata.id'] + r = sut.session.get(sut.rhost + uri) + yield {'uri': uri, 'response': r, 'resource_type': ResourceType.BIOS} if 'Managers' in root: uri = root['Managers']['@odata.id']