From f1ae2365a831e674bda03b64ae33b95e721b32ad Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Thu, 21 Feb 2019 20:11:26 +0100 Subject: [PATCH] Add Managers resource support This change adds basic Redfish Managers resource support based on the newly introduces "manager" driver infrastructure. As of this patch, the only backend for the Redfish manager implemented is static config (Flask) file. Story: 2005149 Task: 29856 Change-Id: I95e957ad02b602410049c5f078c0703e2f0a4962 --- doc/source/admin/emulator.conf | 17 +++ doc/source/user/dynamic-emulator.rst | 68 ++++++++-- ...dd-managers-resource-ffa58e329eccc058.yaml | 6 + sushy_tools/emulator/base.py | 37 +++++ sushy_tools/emulator/main.py | 61 ++++++++- .../emulator/resources/managers/__init__.py | 0 .../emulator/resources/managers/base.py | 69 ++++++++++ .../resources/managers/staticdriver.py | 126 ++++++++++++++++++ sushy_tools/emulator/templates/manager.json | 32 +++++ .../templates/manager_collection.json | 16 +++ sushy_tools/emulator/templates/root.json | 3 + sushy_tools/emulator/templates/system.json | 5 + .../emulator/resources/managers/__init__.py | 0 .../resources/managers/test_static.py | 74 ++++++++++ sushy_tools/tests/unit/emulator/test_main.py | 39 +++++- 15 files changed, 542 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/add-managers-resource-ffa58e329eccc058.yaml create mode 100644 sushy_tools/emulator/base.py create mode 100644 sushy_tools/emulator/resources/managers/__init__.py create mode 100644 sushy_tools/emulator/resources/managers/base.py create mode 100644 sushy_tools/emulator/resources/managers/staticdriver.py create mode 100644 sushy_tools/emulator/templates/manager.json create mode 100644 sushy_tools/emulator/templates/manager_collection.json create mode 100644 sushy_tools/tests/unit/emulator/resources/managers/__init__.py create mode 100644 sushy_tools/tests/unit/emulator/resources/managers/test_static.py diff --git a/doc/source/admin/emulator.conf b/doc/source/admin/emulator.conf index 75fdfeb2..e364193e 100644 --- a/doc/source/admin/emulator.conf +++ b/doc/source/admin/emulator.conf @@ -31,3 +31,20 @@ SUSHY_EMULATOR_BOOT_LOADER_MAP = { 'aarch64': None } } + +# This map contains statically configured Redfish Manager(s) linked +# up with the Systems each Manager pretends to manage. +# +# The first managerc in the list will pretend to manage all other +# resources. +# +# If this map is not present in the configuration, a single default +# Manager is configured automatically to manage all available Systems. +SUSHY_EMULATOR_MANAGERS = [ + { + "Id": "BMC", + "Name": "Manager", + "ServiceEntryPointUUID": "92384634-2938-2342-8820-489239905423", + "UUID": "58893887-8974-2487-2389-841168418919" + } +] diff --git a/doc/source/user/dynamic-emulator.rst b/doc/source/user/dynamic-emulator.rst index 8997a056..b9230c7b 100644 --- a/doc/source/user/dynamic-emulator.rst +++ b/doc/source/user/dynamic-emulator.rst @@ -2,14 +2,24 @@ Virtual Redfish BMC =================== -The virtual Redfish BMC is functionally similar to the +The Virtual Redfish BMC is functionally similar to the `Virtual BMC `_ tool except that the frontend protocol is Redfish rather than IPMI. The Redfish -commands coming from the client get executed against the virtualization -backend. That lets you control virtual machine instances over Redfish. +commands coming from the client are handled by one or more resource-specific +drivers. -The libvirt backend -------------------- +Systems resource +---------------- + +For *Systems* resource, emulator maintains two drivers relying on +a virtualization backend to emulate bare metal machines by means of +virtual machines. + +The following sections will explain how to configure and use +each of these drivers. + +Systems resource driver: libvirt +++++++++++++++++++++++++++++++++ First thing you need is to set up some libvirt-managed virtual machines (AKA domains) to manipulate. The following command will create a new @@ -76,7 +86,7 @@ You can have as many domains as you need. The domains can be concurrently managed over Redfish and some other tool like *Virtual BMC*. UEFI boot -+++++++++ +~~~~~~~~~ By default, `legacy` or `BIOS` mode is used to boot the instance. However, libvirt domain can be configured to boot via UEFI firmware. This process @@ -140,8 +150,8 @@ Now you can run `sushy-emulator` with the updated configuration file: The images you will serve to your VMs need to be UEFI-bootable. -The OpenStack backend ---------------------- +Systems resource driver: OpenStack +++++++++++++++++++++++++++++++++++ You can use an OpenStack cloud instances to simulate Redfish-managed baremetal machines. This setup is known under the name of @@ -203,3 +213,45 @@ And flip its power state via the Redfish call: You can have as many OpenStack instances as you need. The instances can be concurrently managed over Redfish and functionally similar tools. + +Managers resource +----------------- + +For emulating *Managers* resource, the user can statically configure +one or more imaginary Managers. The first configured manager will +pretend to manage all *Systems* and potentially other resources. + +.. code-block:: python + + SUSHY_EMULATOR_MANAGERS = [ + { + "Id": "BMC", + "Name": "Manager", + "ServiceEntryPointUUID": "92384634-2938-2342-8820-489239905423", + "UUID": "58893887-8974-2487-2389-841168418919" + } + ] + +By default a single manager with be configured automatically. + +Managers will be revealed when querying the *Managers* resource +directly, as well as other resources they manage or have some +other relations. + +.. code-block:: bash + + curl http://localhost:8000/redfish/v1/Managers + { + "@odata.type": "#ManagerCollection.ManagerCollection", + "Name": "Manager Collection", + "Members@odata.count": 1, + "Members": [ + + { + "@odata.id": "/redfish/v1/Managers/58893887-8974-2487-2389-841168418919" + } + + ], + "@odata.context": "/redfish/v1/$metadata#ManagerCollection.ManagerCollection", + "@odata.id": "/redfish/v1/Managers", + "@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." diff --git a/releasenotes/notes/add-managers-resource-ffa58e329eccc058.yaml b/releasenotes/notes/add-managers-resource-ffa58e329eccc058.yaml new file mode 100644 index 00000000..9646203c --- /dev/null +++ b/releasenotes/notes/add-managers-resource-ffa58e329eccc058.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds Managers resource emulation to dynamic Redfish emulator. Emulated + Computer Systems link up automatically to the first of the configured + Managers (just one by default). diff --git a/sushy_tools/emulator/base.py b/sushy_tools/emulator/base.py new file mode 100644 index 00000000..9e660bed --- /dev/null +++ b/sushy_tools/emulator/base.py @@ -0,0 +1,37 @@ +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class DriverBase(object): + """Common base for Redfish Systems, Managers and Chassis models""" + + @classmethod + def initialize(cls, **kwargs): + """Initialize class attributes + + Since drivers may need to cache thing short-term. The emulator + instantiates the driver every time it serves a client query. + + Driver objects can cache whenever it makes sense for the duration + of a single session. It is guaranteed that the driver object will + never be reused for any other session. + + The `initialize` method is provided to set up the driver in a way + that would affect all the subsequent sessions. + + :params **kwargs: driver-specific parameters + :returns: initialized driver class + """ + return cls diff --git a/sushy_tools/emulator/main.py b/sushy_tools/emulator/main.py index 4169f154..d39bf3aa 100755 --- a/sushy_tools/emulator/main.py +++ b/sushy_tools/emulator/main.py @@ -14,12 +14,14 @@ # under the License. import argparse +from datetime import datetime import functools import json import os import ssl import sys +from sushy_tools.emulator.resources.managers import staticdriver from sushy_tools.emulator.resources.systems import libvirtdriver from sushy_tools.emulator.resources.systems import novadriver from sushy_tools import error @@ -27,7 +29,6 @@ from sushy_tools.error import FishyError import flask - app = flask.Flask(__name__) # Turn off strict_slashes on all routes app.url_map.strict_slashes = False @@ -36,6 +37,7 @@ app.url_map.strict_slashes = False class Resources(object): SYSTEMS = None + MANAGERS = None def __new__(cls, *args, **kwargs): @@ -75,14 +77,23 @@ class Resources(object): 'Initialized system resource backed by %s ' 'driver', cls.SYSTEMS().driver) + if cls.MANAGERS is None: + cls.MANAGERS = staticdriver.StaticDriver.initialize(app.config) + + app.logger.debug( + 'Initialized manager resource backed by %s ' + 'driver', cls.MANAGERS().driver) + return super(Resources, cls).__new__(cls, *args, **kwargs) def __enter__(self): self.systems = self.SYSTEMS() + self.managers = self.MANAGERS() return self def __exit__(self, exc_type, exc_val, exc_tb): del self.systems + del self.managers def instance_denied(**kwargs): @@ -145,6 +156,50 @@ def root_resource(): return flask.render_template('root.json') +@app.route('/redfish/v1/Managers') +@returns_json +def manager_collection_resource(): + with Resources() as resources: + + app.logger.debug('Serving managers list') + + return flask.render_template( + 'manager_collection.json', + manager_count=len(resources.managers.managers), + managers=resources.managers.managers) + + +@app.route('/redfish/v1/Managers/', methods=['GET']) +@returns_json +def manager_resource(identity): + if flask.request.method == 'GET': + + with Resources() as resources: + + app.logger.debug('Serving resources for manager "%s"', identity) + + managers = resources.managers + + uuid = managers.uuid(identity) + + # the first manager gets all resources + if uuid == managers.managers[0]: + systems = resources.systems.systems + + else: + systems = [] + + return flask.render_template( + 'manager.json', + dateTime=datetime.now().strftime('%Y-%M-%dT%H:%M:%S+00:00'), + identity=identity, + name=resources.managers.name(identity), + uuid=uuid, + serviceEntryPointUUID=resources.managers.uuid(identity), + systems=systems + ) + + @app.route('/redfish/v1/Systems') @returns_json def system_collection_resource(): @@ -167,6 +222,7 @@ def system_resource(identity): app.logger.debug('Serving resources for system "%s"', identity) with Resources() as resources: + return flask.render_template( 'system.json', identity=identity, @@ -176,7 +232,8 @@ def system_resource(identity): total_memory_gb=resources.systems.get_total_memory(identity), total_cpus=resources.systems.get_total_cpus(identity), boot_source_target=resources.systems.get_boot_device(identity), - boot_source_mode=resources.systems.get_boot_mode(identity) + boot_source_mode=resources.systems.get_boot_mode(identity), + managers=resources.managers.managers[:1] ) elif flask.request.method == 'PATCH': diff --git a/sushy_tools/emulator/resources/managers/__init__.py b/sushy_tools/emulator/resources/managers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/emulator/resources/managers/base.py b/sushy_tools/emulator/resources/managers/base.py new file mode 100644 index 00000000..e72a9a91 --- /dev/null +++ b/sushy_tools/emulator/resources/managers/base.py @@ -0,0 +1,69 @@ +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import abc +import six + +from sushy_tools.emulator import base + + +@six.add_metaclass(abc.ABCMeta) +class AbstractManagersDriver(base.DriverBase): + """Base class backing Redfish Managers""" + + @classmethod + def initialize(cls, config): + cls._config = config + return cls + + @abc.abstractproperty + def driver(self): + """Return human-friendly driver information + + :returns: driver information as `str` + """ + + @abc.abstractproperty + def managers(self): + """Return available Redfish managers + + :returns: list of UUIDs representing the managers + """ + + @abc.abstractmethod + def uuid(self, identity): + """Get Redfish manager UUID + + The universal unique identifier (UUID) for this system. Can be used + in place of manager name if there are duplicates. + + If driver backend does not support non-unique manager identity, + this method may just return the `identity`. + + :returns: Redfish manager UUID + """ + + @abc.abstractmethod + def name(self, identity): + """Get Redfish manager name by UUID + + The universal unique identifier (UUID) for this Redfish manager. + Can be used in place of manager name if there are duplicates. + + If driver backend does not support manager names, this method may + just return the `identity`. + + :returns: Redfish manager name + """ diff --git a/sushy_tools/emulator/resources/managers/staticdriver.py b/sushy_tools/emulator/resources/managers/staticdriver.py new file mode 100644 index 00000000..c20b6609 --- /dev/null +++ b/sushy_tools/emulator/resources/managers/staticdriver.py @@ -0,0 +1,126 @@ +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import uuid + +from sushy_tools.emulator.resources.managers.base import AbstractManagersDriver +from sushy_tools import error + + +logger = logging.getLogger(__name__) + + +class StaticDriver(AbstractManagersDriver): + """Redfish manager backed by configuration file""" + + def __init__(self): + + managers = self._config.get('SUSHY_EMULATOR_MANAGERS') + if not managers: + # Default Manager + managers = [ + { + "Id": "BMC", + "Name": "Manager", + "ServiceEntryPointUUID": "92384634-2938-2342-" + "8820-489239905423", + "UUID": "58893887-8974-2487-2389-841168418919", + } + ] + + self._managers_by_id = { + x['Id']: x for x in managers + } + self._managers_by_uuid = { + x['UUID']: x for x in managers if 'UUID' in x + } + self._managers_by_name = { + x['Name']: x for x in managers if 'Name' in x + } + + if len(self._managers_by_uuid) != len(managers): + raise error.FishyError( + 'Conflicting UUIDs in static managers configuration') + + def _get_manager(self, identity): + try: + uu_identity = str(uuid.UUID(identity)) + + return self._managers_by_uuid[uu_identity] + + except (ValueError, KeyError): + + try: + uu_identity = self._managers_by_name[identity]['UUID'] + + except KeyError: + + try: + uu_identity = self._managers_by_id[identity]['UUID'] + + except KeyError: + msg = ('Error finding manager by UUID/Name/Id ' + '"%(identity)s"' % {'identity': identity}) + + logger.debug(msg) + + raise error.FishyError(msg) + + raise error.AliasAccessError(uu_identity) + + @property + def driver(self): + """Return human-friendly driver information + + :returns: driver information as `str` + """ + return '' + + @property + def managers(self): + """Return available Redfish managers + + :returns: list of UUIDs representing the managers + """ + return sorted(self._managers_by_uuid) + + def uuid(self, identity): + """Get Redfish manager UUID + + The universal unique identifier (UUID) for this system. Can be used + in place of manager name if there are duplicates. + + If driver backend does not support non-unique manager identity, + this method may just return the `identity`. + + :returns: Redfish manager UUID + """ + manager = self._get_manager(identity) + return manager.get('UUID') + + def name(self, identity): + """Get Redfish manager name by UUID + + The universal unique identifier (UUID) for this Redfish manager. + Can be used in place of manager name if there are duplicates. + + If driver backend does not support manager names, this method may + just return the `identity`. + + :returns: Redfish manager name + """ + manager = self._get_manager(identity) + return manager.get('Name') diff --git a/sushy_tools/emulator/templates/manager.json b/sushy_tools/emulator/templates/manager.json new file mode 100644 index 00000000..e0550fb6 --- /dev/null +++ b/sushy_tools/emulator/templates/manager.json @@ -0,0 +1,32 @@ +{ + "@odata.type": "#Manager.v1_3_1.Manager", + "Id": {{ identity|string|tojson }}, + "Name": {{ name|string|tojson }}, + "UUID": {{ uuid|string|tojson }}, + "ServiceEntryPointUUID": {{ serviceEntryPointUUID|string|tojson }}, + "ManagerType": "BMC", + "Description": "Contoso BMC", + "Model": "Joo Janta 200", + "DateTime": {{ dateTime|string|tojson }}, + "DateTimeLocalOffset": "+00:00", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "PowerState": "On", + "FirmwareVersion": "1.00", + "Links": { + "ManagerForServers": [ + {% for system in systems %} + { + "@odata.id": {{ "/redfish/v1/Systems/%s"|format(system)|tojson }} + }{% if not loop.last %},{% endif %} + {% endfor %} + ], + "ManagerForChassis": [ + ] + }, + "@odata.context": "/redfish/v1/$metadata#Manager.Manager", + "@odata.id": {{ "/redfish/v1/Managers/%s"|format(identity)|string|tojson }}, + "@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} \ No newline at end of file diff --git a/sushy_tools/emulator/templates/manager_collection.json b/sushy_tools/emulator/templates/manager_collection.json new file mode 100644 index 00000000..ed83001c --- /dev/null +++ b/sushy_tools/emulator/templates/manager_collection.json @@ -0,0 +1,16 @@ +{ + "@odata.type": "#ManagerCollection.ManagerCollection", + "Name": "Manager Collection", + "Members@odata.count": {{ manager_count }}, + "Members": [ + {% for manager in managers %} + { + "@odata.id": {{ "/redfish/v1/Managers/%s"|format(manager)|tojson }} + }{% if not loop.last %},{% endif %} + {% endfor %} + ], + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#ManagerCollection.ManagerCollection", + "@odata.id": "/redfish/v1/Managers", + "@Redfish.Copyright": "Copyright 2014-2017 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/root.json b/sushy_tools/emulator/templates/root.json index ce8c0a02..8f00fc81 100644 --- a/sushy_tools/emulator/templates/root.json +++ b/sushy_tools/emulator/templates/root.json @@ -7,6 +7,9 @@ "Systems": { "@odata.id": "/redfish/v1/Systems" }, + "Managers": { + "@odata.id": "/redfish/v1/Managers" + }, "@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 99c0d44e..f0da0af0 100644 --- a/sushy_tools/emulator/templates/system.json +++ b/sushy_tools/emulator/templates/system.json @@ -73,6 +73,11 @@ "Chassis": [ ], "ManagedBy": [ + {%- for manager in managers %} + { + "@odata.id": {{ "/redfish/v1/Managers/%s"|format(manager)|tojson }} + }{% if not loop.last %},{% endif %} + {% endfor -%} ] }, "Actions": { diff --git a/sushy_tools/tests/unit/emulator/resources/managers/__init__.py b/sushy_tools/tests/unit/emulator/resources/managers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/tests/unit/emulator/resources/managers/test_static.py b/sushy_tools/tests/unit/emulator/resources/managers/test_static.py new file mode 100644 index 00000000..7cd13c31 --- /dev/null +++ b/sushy_tools/tests/unit/emulator/resources/managers/test_static.py @@ -0,0 +1,74 @@ +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import uuid + +from oslotest import base + +from sushy_tools.emulator.resources.managers.staticdriver import StaticDriver +from sushy_tools import error + + +class StaticDriverTestCase(base.BaseTestCase): + + def setUp(self): + self.managers = [ + { + "Id": "BMC", + "Name": "The manager", + "UUID": "58893887-8974-2487-2389-841168418919", + "ServiceEntryPointUUID": "92384634-2938-2342-8820-489239905423" + } + ] + + self.identity = self.managers[0]['Id'] + self.uuid = self.managers[0]['UUID'] + self.name = self.managers[0]['Name'] + + test_driver = StaticDriver.initialize( + {'SUSHY_EMULATOR_MANAGERS': self.managers}) + self.test_driver = test_driver() + super(StaticDriverTestCase, self).setUp() + + def test__get_manager_by_id(self): + self.assertRaises( + error.AliasAccessError, self.test_driver._get_manager, + self.identity) + + def test__get_manager_by_name(self): + self.assertRaises( + error.AliasAccessError, self.test_driver._get_manager, self.name) + + def test__get_manager_by_uuid(self): + domain_id = uuid.UUID(self.uuid) + manager = self.test_driver._get_manager(str(domain_id)) + self.assertEqual( + self.managers[0], manager) + + def test_uuid_ok(self): + self.assertEqual(self.uuid, self.test_driver.uuid(self.uuid)) + + def test_uuid_fail(self): + self.assertRaises(error.FishyError, self.test_driver.uuid, 'xxx') + + def test_name_ok(self): + self.assertRaises(error.AliasAccessError, + self.test_driver.name, self.name) + + def test_name_fail(self): + self.assertRaises(error.FishyError, self.test_driver.name, 'xxx') + + def test_managers(self): + managers = self.test_driver.managers + self.assertEqual([self.uuid], managers) diff --git a/sushy_tools/tests/unit/emulator/test_main.py b/sushy_tools/tests/unit/emulator/test_main.py index 7896c3e4..5e29b01a 100644 --- a/sushy_tools/tests/unit/emulator/test_main.py +++ b/sushy_tools/tests/unit/emulator/test_main.py @@ -40,9 +40,41 @@ class EmulatorTestCase(base.BaseTestCase): self.assertEqual(200, response.status_code) self.assertEqual('RedvirtService', response.json['Id']) - def test_collection_resource(self, resources_mock): + def test_manager_collection_resource(self, resources_mock): + resources_mock = resources_mock.return_value.__enter__.return_value + managers_mock = resources_mock.managers + type(managers_mock).managers = mock.PropertyMock( + return_value=['bmc0', 'bmc1']) + response = self.app.get('/redfish/v1/Managers') + self.assertEqual(200, response.status_code) + self.assertEqual({'@odata.id': '/redfish/v1/Managers/bmc0'}, + response.json['Members'][0]) + self.assertEqual({'@odata.id': '/redfish/v1/Managers/bmc1'}, + response.json['Members'][1]) + + def test_manager_resource_get(self, resources_mock): resources_mock = resources_mock.return_value.__enter__.return_value systems_mock = resources_mock.systems + systems_mock.systems = ['xxx'] + managers_mock = resources_mock.managers + managers_mock.managers = ['xxxx-yyyy-zzzz'] + managers_mock.uuid.return_value = 'xxxx-yyyy-zzzz' + managers_mock.name.return_value = 'name' + + response = self.app.get('/redfish/v1/Managers/xxxx-yyyy-zzzz') + + self.assertEqual(200, response.status_code) + self.assertEqual('xxxx-yyyy-zzzz', response.json['Id']) + self.assertEqual('xxxx-yyyy-zzzz', response.json['UUID']) + self.assertEqual('xxxx-yyyy-zzzz', + response.json['ServiceEntryPointUUID']) + self.assertEqual([{'@odata.id': '/redfish/v1/Systems/xxx'}], + response.json['Links']['ManagerForServers']) + + def test_system_collection_resource(self, resources_mock): + resources_mock = resources_mock.return_value.__enter__.return_value + systems_mock = resources_mock.systems + type(systems_mock).systems = mock.PropertyMock( return_value=['host0', 'host1']) response = self.app.get('/redfish/v1/Systems') @@ -61,6 +93,8 @@ class EmulatorTestCase(base.BaseTestCase): systems_mock.get_total_cpus.return_value = 2 systems_mock.get_boot_device.return_value = 'Cd' systems_mock.get_boot_mode.return_value = 'Legacy' + managers_mock = resources_mock.managers + managers_mock.managers = ['aaaa-bbbb-cccc'] response = self.app.get('/redfish/v1/Systems/xxxx-yyyy-zzzz') @@ -75,6 +109,9 @@ class EmulatorTestCase(base.BaseTestCase): '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']) def test_system_resource_patch(self, resources_mock): resources_mock = resources_mock.return_value.__enter__.return_value