diff --git a/doc/source/user/dynamic-emulator.rst b/doc/source/user/dynamic-emulator.rst index d49a3fd9..c2b12538 100644 --- a/doc/source/user/dynamic-emulator.rst +++ b/doc/source/user/dynamic-emulator.rst @@ -8,6 +8,19 @@ except that the frontend protocol is Redfish rather than IPMI. The Redfish commands coming from the client are handled by one or more resource-specific drivers. +Feature sets +------------ + +The emulator can be configured with different feature sets to emulate different +hardware. The feature set is supplied either via the +``SUSHY_EMULATOR_FEATURE_SET`` configuration variable or through the +``--feature-set`` command line flag. + +Supported feature sets are: +* ``minimum`` - only Systems with Boot settings and no other optional fields. +* ``vmedia`` - ``minimum`` plus Managers, VirtualMedia and EthernetInterfaces. +* ``full`` - all features implemented in the emulator. + Systems resource ---------------- diff --git a/releasenotes/notes/feature-set-42f2846a17f59424.yaml b/releasenotes/notes/feature-set-42f2846a17f59424.yaml new file mode 100644 index 00000000..a76c541d --- /dev/null +++ b/releasenotes/notes/feature-set-42f2846a17f59424.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a configuration options ``SUSHY_EMULATOR_FEATURE_SET`` to define which + resources should be available. See the documentation for more details. diff --git a/sushy_tools/emulator/main.py b/sushy_tools/emulator/main.py index 4e035e11..94461ff1 100755 --- a/sushy_tools/emulator/main.py +++ b/sushy_tools/emulator/main.py @@ -98,6 +98,18 @@ class Application(flask.Flask): if auth_file and not isinstance(self.wsgi_app, RedfishAuthMiddleware): self.wsgi_app = RedfishAuthMiddleware(self.wsgi_app, auth_file) + feature_set = self.config.get('SUSHY_EMULATOR_FEATURE_SET', 'full') + if feature_set not in ('full', 'vmedia', 'minimum'): + raise RuntimeError(f"Invalid feature set {self.feature_set}") + + @property + def feature_set(self): + return self.config.get('SUSHY_EMULATOR_FEATURE_SET', 'full') + + def render_template(self, template_name, /, **params): + params.setdefault('feature_set', self.feature_set) + return flask.render_template(template_name, **params) + @property @memoize.memoize() def systems(self): @@ -203,15 +215,18 @@ def all_exception_handler(message): @app.route('/redfish/v1/') @api_utils.returns_json def root_resource(): - return flask.render_template('root.json') + return app.render_template('root.json') @app.route('/redfish/v1/Chassis') @api_utils.returns_json def chassis_collection_resource(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Chassis") + app.logger.debug('Serving chassis list') - return flask.render_template( + return app.render_template( 'chassis_collection.json', manager_count=len(app.chassis.chassis), chassis=app.chassis.chassis) @@ -220,6 +235,9 @@ def chassis_collection_resource(): @app.route('/redfish/v1/Chassis/', methods=['GET', 'PATCH']) @api_utils.returns_json def chassis_resource(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Chassis") + chassis = app.chassis uuid = chassis.uuid(identity) @@ -241,7 +259,7 @@ def chassis_resource(identity): storage = [] drives = [] - return flask.render_template( + return app.render_template( 'chassis.json', identity=identity, name=chassis.name(identity), @@ -272,6 +290,9 @@ def chassis_resource(identity): @app.route('/redfish/v1/Chassis//Thermal', methods=['GET']) @api_utils.returns_json def thermal_resource(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Chassis") + chassis = app.chassis uuid = chassis.uuid(identity) @@ -286,7 +307,7 @@ def thermal_resource(identity): else: systems = [] - return flask.render_template( + return app.render_template( 'thermal.json', chassis=identity, systems=systems @@ -296,9 +317,12 @@ def thermal_resource(identity): @app.route('/redfish/v1/Managers') @api_utils.returns_json def manager_collection_resource(): + if app.feature_set == "minimum": + raise error.FeatureNotAvailable("Managers") + app.logger.debug('Serving managers list') - return flask.render_template( + return app.render_template( 'manager_collection.json', manager_count=len(app.managers.managers), managers=app.managers.managers) @@ -319,6 +343,9 @@ def jsonify(obj_type, obj_version, obj): @app.route('/redfish/v1/Managers/', methods=['GET']) @api_utils.returns_json def manager_resource(identity): + if app.feature_set == "minimum": + raise error.FeatureNotAvailable("Managers") + app.logger.debug('Serving resources for manager "%s"', identity) manager = app.managers.get_manager(identity) @@ -326,22 +353,11 @@ def manager_resource(identity): chassis = app.managers.get_managed_chassis(manager) uuid = manager['UUID'] - return jsonify('Manager', 'v1_3_1', { + result = { "Id": manager['Id'], "Name": manager.get('Name'), "UUID": uuid, - "ServiceEntryPointUUID": manager.get('ServiceEntryPointUUID'), "ManagerType": "BMC", - "Description": "Contoso BMC", - "Model": "Joo Janta 200", - "DateTime": datetime.now().strftime('%Y-%M-%dT%H:%M:%S+00:00'), - "DateTimeLocalOffset": "+00:00", - "Status": { - "State": "Enabled", - "Health": "OK" - }, - "PowerState": "On", - "FirmwareVersion": "1.00", "VirtualMedia": { "@odata.id": "/redfish/v1/Systems/%s/VirtualMedia" % systems[0], }, @@ -356,11 +372,27 @@ def manager_resource(identity): { "@odata.id": "/redfish/v1/Chassis/%s" % ch } - for ch in chassis + for ch in chassis if app.feature_set == "full" ] }, - "@odata.id": "/redfish/v1/Managers/%s" % uuid - }) + "@odata.id": "/redfish/v1/Managers/%s" % uuid, + } + if app.feature_set == "full": + result.update({ + "ServiceEntryPointUUID": manager.get('ServiceEntryPointUUID'), + "Description": "Contoso BMC", + "Model": "Joo Janta 200", + "DateTime": datetime.now().strftime('%Y-%M-%dT%H:%M:%S+00:00'), + "DateTimeLocalOffset": "+00:00", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "PowerState": "On", + "FirmwareVersion": "1.00", + }) + + return jsonify('Manager', 'v1_3_1', result) @app.route('/redfish/v1/Systems') @@ -371,7 +403,7 @@ def system_collection_resource(): app.logger.debug('Serving systems list') - return flask.render_template( + return app.render_template( 'system_collection.json', system_count=len(systems), systems=systems) @@ -390,7 +422,7 @@ def system_resource(identity): except error.NotSupportedError: return None - return flask.render_template( + return app.render_template( 'system.json', identity=identity, name=app.systems.name(identity), @@ -414,6 +446,8 @@ def system_resource(identity): if not boot and not indicator_led_state: return ('PATCH only works for Boot and ' 'IndicatorLED elements'), 400 + if indicator_led_state and app.feature_set != "full": + raise error.FeatureNotAvailable("IndicatorLED", code=400) if boot: target = boot.get('BootSourceOverrideTarget') @@ -492,9 +526,12 @@ def system_resource(identity): @api_utils.ensure_instance_access @api_utils.returns_json def ethernet_interfaces_collection(identity): + if app.feature_set == "minimum": + raise error.FeatureNotAvailable("EthernetInterfaces") + nics = app.systems.get_nics(identity) - return flask.render_template( + return app.render_template( 'ethernet_interfaces_collection.json', identity=identity, nics=nics) @@ -504,11 +541,14 @@ def ethernet_interfaces_collection(identity): @api_utils.ensure_instance_access @api_utils.returns_json def ethernet_interface(identity, nic_id): + if app.feature_set == "minimum": + raise error.FeatureNotAvailable("EthernetInterfaces") + nics = app.systems.get_nics(identity) for nic in nics: if nic['id'] == nic_id: - return flask.render_template( + return app.render_template( 'ethernet_interface.json', identity=identity, nic=nic) raise error.NotFound() @@ -519,9 +559,12 @@ def ethernet_interface(identity, nic_id): @api_utils.ensure_instance_access @api_utils.returns_json def processors_collection(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Processors") + processors = app.systems.get_processors(identity) - return flask.render_template( + return app.render_template( 'processors_collection.json', identity=identity, processors=processors) @@ -531,11 +574,14 @@ def processors_collection(identity): @api_utils.ensure_instance_access @api_utils.returns_json def processor(identity, processor_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Processors") + processors = app.systems.get_processors(identity) for proc in processors: if proc['id'] == processor_id: - return flask.render_template( + return app.render_template( 'processor.json', identity=identity, processor=proc) raise error.NotFound() @@ -560,11 +606,14 @@ def system_reset_action(identity): @api_utils.ensure_instance_access @api_utils.returns_json def bios(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("BIOS") + bios = app.systems.get_bios(identity) app.logger.debug('Serving BIOS for system "%s"', identity) - return flask.render_template( + return app.render_template( 'bios.json', identity=identity, bios_current_attributes=json.dumps(bios, sort_keys=True, indent=6)) @@ -575,13 +624,15 @@ def bios(identity): @api_utils.ensure_instance_access @api_utils.returns_json def bios_settings(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("BIOS") if flask.request.method == 'GET': bios = app.systems.get_bios(identity) app.logger.debug('Serving BIOS Settings for system "%s"', identity) - return flask.render_template( + return app.render_template( 'bios_settings.json', identity=identity, bios_pending_attributes=json.dumps(bios, sort_keys=True, indent=6)) @@ -601,6 +652,9 @@ def bios_settings(identity): @api_utils.ensure_instance_access @api_utils.returns_json def system_reset_bios(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("BIOS") + app.systems.reset_bios(identity) app.logger.info('BIOS for system "%s" reset', identity) @@ -613,13 +667,15 @@ def system_reset_bios(identity): @api_utils.ensure_instance_access @api_utils.returns_json def secure_boot(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("SecureBoot") if flask.request.method == 'GET': secure = app.systems.get_secure_boot(identity) app.logger.debug('Serving secure boot for system "%s"', identity) - return flask.render_template( + return app.render_template( 'secure_boot.json', identity=identity, secure_boot_enable=secure, @@ -640,10 +696,13 @@ def secure_boot(identity): @api_utils.ensure_instance_access @api_utils.returns_json def simple_storage_collection(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("SimpleStorage") + simple_storage_controllers = ( app.systems.get_simple_storage_collection(identity)) - return flask.render_template( + return app.render_template( 'simple_storage_collection.json', identity=identity, simple_storage_controllers=simple_storage_controllers) @@ -653,6 +712,9 @@ def simple_storage_collection(identity): @api_utils.ensure_instance_access @api_utils.returns_json def simple_storage(identity, simple_storage_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("SimpleStorage") + simple_storage_controllers = ( app.systems.get_simple_storage_collection(identity)) try: @@ -660,8 +722,8 @@ def simple_storage(identity, simple_storage_id): except KeyError: app.logger.debug('"%s" Simple Storage resource was not found') raise error.NotFound() - return flask.render_template('simple_storage.json', identity=identity, - simple_storage=storage_controller) + return app.render_template('simple_storage.json', identity=identity, + simple_storage=storage_controller) @app.route('/redfish/v1/Systems//Storage', @@ -669,11 +731,14 @@ def simple_storage(identity, simple_storage_id): @api_utils.ensure_instance_access @api_utils.returns_json def storage_collection(identity): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Storage") + uuid = app.systems.uuid(identity) storage_col = app.storage.get_storage_col(uuid) - return flask.render_template( + return app.render_template( 'storage_collection.json', identity=identity, storage_col=storage_col) @@ -683,12 +748,15 @@ def storage_collection(identity): @api_utils.ensure_instance_access @api_utils.returns_json def storage(identity, storage_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Storage") + uuid = app.systems.uuid(identity) storage_col = app.storage.get_storage_col(uuid) for stg in storage_col: if stg['Id'] == storage_id: - return flask.render_template( + return app.render_template( 'storage.json', identity=identity, storage=stg) raise error.NotFound() @@ -699,12 +767,15 @@ def storage(identity, storage_id): @api_utils.ensure_instance_access @api_utils.returns_json def drive_resource(identity, stg_id, drv_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Storage") + uuid = app.systems.uuid(identity) drives = app.drives.get_drives(uuid, stg_id) for drv in drives: if drv['Id'] == drv_id: - return flask.render_template( + return app.render_template( 'drive.json', identity=identity, storage_id=stg_id, drive=drv) raise error.NotFound() @@ -715,6 +786,9 @@ def drive_resource(identity, stg_id, drv_id): @api_utils.ensure_instance_access @api_utils.returns_json def volumes_collection(identity, storage_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Storage") + uuid = app.systems.uuid(identity) if flask.request.method == 'GET': @@ -729,7 +803,7 @@ def volumes_collection(identity, storage_id): else: vol_ids.append(vol_id) - return flask.render_template( + return app.render_template( 'volume_collection.json', identity=identity, storage_id=storage_id, volume_col=vol_ids) @@ -757,6 +831,9 @@ def volumes_collection(identity, storage_id): @api_utils.ensure_instance_access @api_utils.returns_json def volume(identity, stg_id, vol_id): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Storage") + uuid = app.systems.uuid(identity) vol_col = app.volumes.get_volumes_col(uuid, stg_id) @@ -766,7 +843,7 @@ def volume(identity, stg_id, vol_id): if not vol_id: app.volumes.delete_volume(uuid, stg_id, vol) else: - return flask.render_template( + return app.render_template( 'volume.json', identity=identity, storage_id=stg_id, volume=vol) @@ -776,44 +853,59 @@ def volume(identity, stg_id, vol_id): @app.route('/redfish/v1/Registries') @api_utils.returns_json def registry_file_collection(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Registries") + app.logger.debug('Serving registry file collection') - return flask.render_template( + return app.render_template( 'registry_file_collection.json') @app.route('/redfish/v1/Registries/BiosAttributeRegistry.v1_0_0') @api_utils.returns_json def bios_attribute_registry_file(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Registries") + app.logger.debug('Serving BIOS attribute registry file') - return flask.render_template( + return app.render_template( 'bios_attribute_registry_file.json') @app.route('/redfish/v1/Registries/Messages') @api_utils.returns_json def message_registry_file(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Registries") + app.logger.debug('Serving message registry file') - return flask.render_template( + return app.render_template( 'message_registry_file.json') @app.route('/redfish/v1/Systems/Bios/BiosRegistry') @api_utils.returns_json def bios_registry(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Registries") + app.logger.debug('Serving BIOS registry') - return flask.render_template('bios_registry.json') + return app.render_template('bios_registry.json') @app.route('/redfish/v1/Registries/Messages/Registry') @api_utils.returns_json def message_registry(): + if app.feature_set != "full": + raise error.FeatureNotAvailable("Registries") + app.logger.debug('Serving message registry') - return flask.render_template('message_registry.json') + return app.render_template('message_registry.json') def parse_args(): @@ -843,6 +935,10 @@ def parse_args(): type=str, help='SSL key to use for HTTPS. Can also be set' 'via config variable SUSHY_EMULATOR_SSL_KEY.') + parser.add_argument('--feature-set', + type=str, choices=['full', 'vmedia', 'minimum'], + help='Feature set to provide. Can also be set' + 'via config variable SUSHY_EMULATOR_FEATURE_SET.') backend_group = parser.add_mutually_exclusive_group() backend_group.add_argument('--os-cloud', type=str, @@ -908,6 +1004,9 @@ def main(): if args.ssl_key: app.config['SUSHY_EMULATOR_SSL_KEY'] = args.ssl_key + if args.feature_set: + app.config['SUSHY_EMULATOR_FEATURE_SET'] = args.feature_set + ssl_context = None ssl_certificate = app.config.get('SUSHY_EMULATOR_SSL_CERT') ssl_key = app.config.get('SUSHY_EMULATOR_SSL_KEY') diff --git a/sushy_tools/emulator/templates/root.json b/sushy_tools/emulator/templates/root.json index feff398a..e4b0e2ed 100644 --- a/sushy_tools/emulator/templates/root.json +++ b/sushy_tools/emulator/templates/root.json @@ -4,21 +4,27 @@ "Name": "Redvirt Service", "RedfishVersion": "1.5.0", "UUID": "85775665-c110-4b85-8989-e6162170b3ec", + {% if feature_set == "full" %} "Chassis": { "@odata.id": "/redfish/v1/Chassis" }, + {% endif %} "Systems": { "@odata.id": "/redfish/v1/Systems" }, + {% if feature_set != "minimum" %} "Managers": { "@odata.id": "/redfish/v1/Managers" }, + {% endif %} + {% if feature_set == "full" %} "Registries": { "@odata.id": "/redfish/v1/Registries" }, "CertificateService": { "@odata.id": "/redfish/v1/CertificateService" }, + {% endif %} "@odata.id": "/redfish/v1/", "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." } diff --git a/sushy_tools/emulator/templates/system.json b/sushy_tools/emulator/templates/system.json index eedc8e4c..e7ad142a 100644 --- a/sushy_tools/emulator/templates/system.json +++ b/sushy_tools/emulator/templates/system.json @@ -3,12 +3,14 @@ "Id": {{ identity|string|tojson }}, "Name": {{ name|string|tojson }}, "UUID": {{ uuid|string|tojson }}, + {%- if feature_set == "full" %} "Manufacturer": "Sushy Emulator", "Status": { "State": "Enabled", "Health": "OK", "HealthRollUp": "OK" }, + {% endif %} {%- if power_state %} "PowerState": {{ power_state|string|tojson }}, {%- endif %} @@ -40,6 +42,7 @@ "BootSourceOverrideEnabled": "Continuous" {%- endif %} }, + {%- if feature_set == "full" %} "ProcessorSummary": { {%- if total_cpus %} "Count": {{ total_cpus }}, @@ -70,9 +73,6 @@ "Memory": { "@odata.id": {{ "/redfish/v1/Systems/%s/Memory"|format(identity)|tojson }} }, - "EthernetInterfaces": { - "@odata.id": {{ "/redfish/v1/Systems/%s/EthernetInterfaces"|format(identity)|tojson }} - }, "SecureBoot": { "@odata.id": {{ "/redfish/v1/Systems/%s/SecureBoot"|format(identity)|tojson }} }, @@ -85,10 +85,17 @@ {%- if indicator_led %} "IndicatorLED": {{ indicator_led|string|tojson }}, {%- endif %} + {%- endif %} + {%- if feature_set != "minimum" %} + "EthernetInterfaces": { + "@odata.id": {{ "/redfish/v1/Systems/%s/EthernetInterfaces"|format(identity)|tojson }} + }, "VirtualMedia": { "@odata.id": {{ "/redfish/v1/Systems/%s/VirtualMedia"|format(identity)|tojson }} }, + {%- endif %} "Links": { + {%- if feature_set == "full" %} "Chassis": [ {%- for chassis_ in chassis %} { @@ -96,6 +103,8 @@ }{% if not loop.last %},{% endif %} {% endfor -%} ], + {% endif %} + {%- if feature_set != "minimum" %} "ManagedBy": [ {%- for manager in managers %} { @@ -103,6 +112,7 @@ }{% if not loop.last %},{% endif %} {% endfor -%} ] + {% endif %} }, "Actions": { "#ComputerSystem.Reset": { diff --git a/sushy_tools/error.py b/sushy_tools/error.py index 3b27e724..e22f1669 100644 --- a/sushy_tools/error.py +++ b/sushy_tools/error.py @@ -45,3 +45,10 @@ class BadRequest(FishyError): def __init__(self, msg, code=400): super().__init__(msg, code) + + +class FeatureNotAvailable(NotFound): + """Feature is not available.""" + + def __init__(self, feature, code=404): + super().__init__(f"Feature {feature} not available", code=code) diff --git a/sushy_tools/tests/unit/emulator/test_main.py b/sushy_tools/tests/unit/emulator/test_main.py index 0294e164..343d5d17 100644 --- a/sushy_tools/tests/unit/emulator/test_main.py +++ b/sushy_tools/tests/unit/emulator/test_main.py @@ -36,6 +36,12 @@ class EmulatorTestCase(base.BaseTestCase): super(EmulatorTestCase, self).setUp() + def set_feature_set(self, new_feature_set): + main.app.config['SUSHY_EMULATOR_FEATURE_SET'] = new_feature_set + self.addCleanup( + lambda: main.app.config.pop('SUSHY_EMULATOR_FEATURE_SET', None) + ) + class CommonTestCase(EmulatorTestCase): @@ -52,6 +58,22 @@ class CommonTestCase(EmulatorTestCase): self.assertEqual(200, response.status_code) self.assertEqual('RedvirtService', response.json['Id']) + def test_root_resource_only_vmedia(self): + self.set_feature_set("vmedia") + response = self.app.get('/redfish/v1/') + self.assertEqual(200, response.status_code) + self.assertEqual( + {'Id', 'Name', 'RedfishVersion', 'UUID', 'Systems', 'Managers'}, + {x for x in response.json if not x.startswith('@')}) + + def test_root_resource_minimum(self): + self.set_feature_set("minimum") + response = self.app.get('/redfish/v1/') + self.assertEqual(200, response.status_code) + self.assertEqual( + {'Id', 'Name', 'RedfishVersion', 'UUID', 'Systems'}, + {x for x in response.json if not x.startswith('@')}) + TEST_PASSWD = \ b"admin:$2y$05$mYl8KMwM94l4LR/sw1teIeA6P2u8gfX16e8wvT7NmGgAM5r9jgLl." @@ -210,6 +232,33 @@ class ManagersTestCase(EmulatorTestCase): response.json['Links']['ManagerForServers']) self.assertEqual([{'@odata.id': '/redfish/v1/Chassis/chassis0'}], response.json['Links']['ManagerForChassis']) + self.assertEqual({'@odata.id': '/redfish/v1/Systems/xxx/VirtualMedia'}, + response.json['VirtualMedia']) + + @patch_resource('managers') + def test_manager_resource_get_reduced_feature_set(self, managers_mock): + self.set_feature_set("vmedia") + managers_mock = managers_mock.return_value + managers_mock.managers = ['xxxx-yyyy-zzzz'] + managers_mock.get_manager.return_value = { + 'UUID': 'xxxx-yyyy-zzzz', + 'Name': 'name', + 'Id': 'xxxx-yyyy-zzzz', + } + managers_mock.get_managed_systems.return_value = ['xxx'] + managers_mock.get_managed_chassis.return_value = ['chassis0'] + + response = self.app.get('/redfish/v1/Managers/xxxx-yyyy-zzzz') + + self.assertEqual(200, response.status_code, response.json) + self.assertEqual('xxxx-yyyy-zzzz', response.json['Id']) + self.assertEqual('xxxx-yyyy-zzzz', response.json['UUID']) + self.assertNotIn('ServiceEntryPointUUID', response.json) + self.assertEqual([{'@odata.id': '/redfish/v1/Systems/xxx'}], + response.json['Links']['ManagerForServers']) + self.assertNotIn('Chassis', response.json['Links']) + self.assertEqual({'@odata.id': '/redfish/v1/Systems/xxx/VirtualMedia'}, + response.json['VirtualMedia']) class SystemsTestCase(EmulatorTestCase): @@ -263,6 +312,81 @@ class SystemsTestCase(EmulatorTestCase): self.assertEqual( [{'@odata.id': '/redfish/v1/Chassis/chassis0'}], response.json['Links']['Chassis']) + self.assertEqual( + {'@odata.id': '/redfish/v1/Systems/xxxx-yyyy-zzzz/VirtualMedia'}, + response.json['VirtualMedia']) + + @patch_resource('indicators') + @patch_resource('chassis') + @patch_resource('managers') + @patch_resource('systems') + def test_system_resource_get_reduced_feature_set( + self, systems_mock, managers_mock, chassis_mock, indicators_mock): + self.set_feature_set("vmedia") + systems_mock = systems_mock.return_value + systems_mock.uuid.return_value = 'zzzz-yyyy-xxxx' + systems_mock.get_power_state.return_value = 'On' + systems_mock.get_boot_device.return_value = 'Cd' + systems_mock.get_boot_mode.return_value = 'Legacy' + managers_mock.return_value.get_managers_for_system.return_value = [ + 'aaaa-bbbb-cccc'] + + response = self.app.get('/redfish/v1/Systems/xxxx-yyyy-zzzz') + + self.assertEqual(200, response.status_code) + self.assertEqual('xxxx-yyyy-zzzz', response.json['Id']) + self.assertEqual('zzzz-yyyy-xxxx', response.json['UUID']) + self.assertEqual('On', response.json['PowerState']) + self.assertNotIn('IndicatorLED', response.json) + self.assertNotIn('MemorySummary', response.json) + self.assertNotIn('ProcessorSummary', response.json) + self.assertNotIn('BiosVersion', response.json) + self.assertNotIn('Bios', response.json) + self.assertEqual( + 'Cd', response.json['Boot']['BootSourceOverrideTarget']) + self.assertEqual( + 'Legacy', response.json['Boot']['BootSourceOverrideMode']) + self.assertEqual( + [{'@odata.id': '/redfish/v1/Managers/aaaa-bbbb-cccc'}], + response.json['Links']['ManagedBy']) + self.assertNotIn('Chassis', response.json['Links']) + self.assertEqual( + {'@odata.id': '/redfish/v1/Systems/xxxx-yyyy-zzzz/VirtualMedia'}, + response.json['VirtualMedia']) + + @patch_resource('indicators') + @patch_resource('chassis') + @patch_resource('managers') + @patch_resource('systems') + def test_system_resource_get_minimum_feature_set( + self, systems_mock, managers_mock, chassis_mock, indicators_mock): + self.set_feature_set("minimum") + systems_mock = systems_mock.return_value + systems_mock.uuid.return_value = 'zzzz-yyyy-xxxx' + systems_mock.get_power_state.return_value = 'On' + systems_mock.get_boot_device.return_value = 'Cd' + systems_mock.get_boot_mode.return_value = 'Legacy' + managers_mock.return_value.get_managers_for_system.return_value = [ + 'aaaa-bbbb-cccc'] + + response = self.app.get('/redfish/v1/Systems/xxxx-yyyy-zzzz') + + self.assertEqual(200, response.status_code) + self.assertEqual('xxxx-yyyy-zzzz', response.json['Id']) + self.assertEqual('zzzz-yyyy-xxxx', response.json['UUID']) + self.assertEqual('On', response.json['PowerState']) + self.assertNotIn('IndicatorLED', response.json) + self.assertNotIn('MemorySummary', response.json) + self.assertNotIn('ProcessorSummary', response.json) + self.assertNotIn('BiosVersion', response.json) + self.assertNotIn('Bios', response.json) + self.assertEqual( + 'Cd', response.json['Boot']['BootSourceOverrideTarget']) + self.assertEqual( + 'Legacy', response.json['Boot']['BootSourceOverrideMode']) + self.assertNotIn('ManagedBy', response.json['Links']) + self.assertNotIn('Chassis', response.json['Links']) + self.assertNotIn('VirtualMedia', response.json) @patch_resource('systems') def test_system_resource_patch(self, systems_mock): @@ -336,6 +460,18 @@ class SystemsTestCase(EmulatorTestCase): json=data) self.assertEqual(500, response.status_code) + @patch_resource('indicators') + @patch_resource('systems') + def test_system_indicator_reduced_feature_set(self, systems_mock, + indicators_mock): + self.set_feature_set("vmedia") + systems_mock.return_value.uuid.return_value = self.uuid + + data = {'IndicatorLED': 'Off'} + response = self.app.patch('/redfish/v1/Systems/xxxx-yyyy-zzzz', + json=data) + self.assertEqual(400, response.status_code) + @patch_resource('systems') class BiosTestCase(EmulatorTestCase):