Add System<->Manager linkage

Redfish data model rests on three interlinked
entities - ComputerSystem(s), Manager(s) and Chassis. As of this
moment, sushy does not support traversing between these entities
at the Sushy abstraction level, despite the availability of such
linkage in the JSON documents sushy feeds on.

This change establishes System->Managers and Managers->System
links.

Change-Id: I54b0fdeebdea1e13c2b6912ee4c97776ebccaf03
Story: 2004512
Task: 28240
This commit is contained in:
Ilya Etingof 2018-12-03 15:41:25 +01:00
parent 5fbc00ed91
commit 0bf7cbf14d
7 changed files with 100 additions and 6 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
Establishes ComputerSystem->Managers and Manager->ComputerSystems
references at sushy data abstraction level what make it possible to
look up Manager(s) responsible for a ComputerSystem and vice versa.

View File

@ -188,6 +188,25 @@ class Manager(base.ResourceBase):
self._conn, utils.get_sub_resource_path_by(self, 'VirtualMedia'),
redfish_version=self.redfish_version)
@property
@utils.cache_it
def systems(self):
"""A list of systems managed by this manager.
Returns a list of `System` objects representing systems being
managed by this manager.
:raises: MissingAttributeError if '@odata.id' field is missing.
:returns: A list of `System` instances
"""
paths = utils.get_sub_resource_path_by(
self, ["Links", "ManagerForServers"], is_collection=True)
from sushy.resources.system import system
return [system.System(self._conn, path,
redfish_version=self.redfish_version)
for path in paths]
class ManagerCollection(base.ResourceCollectionBase):

View File

@ -18,6 +18,7 @@ import logging
from sushy import exceptions
from sushy.resources import base
from sushy.resources import common
from sushy.resources.manager import manager
from sushy.resources import mappings as res_maps
from sushy.resources.system import bios
from sushy.resources.system import constants as sys_cons
@ -333,6 +334,24 @@ class System(base.ResourceBase):
self._conn, utils.get_sub_resource_path_by(self, "Storage"),
redfish_version=self.redfish_version)
@property
@utils.cache_it
def managers(self):
"""A list of managers for this system.
Returns a list of `Manager` objects representing the managers
that manage this system.
:raises: MissingAttributeError if '@odata.id' field is missing.
:returns: A list of `Manager` instances
"""
paths = utils.get_sub_resource_path_by(
self, ["Links", "ManagedBy"], is_collection=True)
return [manager.Manager(self._conn, path,
redfish_version=self.redfish_version)
for path in paths]
class SystemCollection(base.ResourceCollectionBase):

View File

@ -18,6 +18,7 @@ import sushy
from sushy import exceptions
from sushy.resources.manager import manager
from sushy.resources.manager import virtual_media
from sushy.resources.system import system
from sushy.tests.unit import base
@ -265,6 +266,18 @@ class ManagerTestCase(base.TestCase):
virtual_media.VirtualMediaCollection)
self.assertFalse(vrt_media._is_stale)
def test_systems(self):
# | GIVEN |
with open('sushy/tests/unit/json_samples/'
'system.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
# | WHEN & THEN |
actual_systems = self.manager.systems
self.assertIsInstance(actual_systems[0], system.System)
self.assertEqual(
'/redfish/v1/Systems/437XR1138R2', actual_systems[0].path)
class ManagerCollectionTestCase(base.TestCase):

View File

@ -20,6 +20,7 @@ import mock
import sushy
from sushy import exceptions
from sushy.resources import constants as res_cons
from sushy.resources.manager import manager
from sushy.resources.system import bios
from sushy.resources.system import mappings as sys_map
from sushy.resources.system import processor
@ -478,6 +479,18 @@ class SystemTestCase(base.TestCase):
# | WHEN & THEN |
self.assertIsInstance(self.sys_inst.storage, storage.StorageCollection)
def test_managers(self):
# | GIVEN |
with open('sushy/tests/unit/json_samples/'
'manager.json') as f:
self.conn.get.return_value.json.return_value = json.load(f)
# | WHEN & THEN |
actual_managers = self.sys_inst.managers
self.assertIsInstance(actual_managers[0], manager.Manager)
self.assertEqual(
'/redfish/v1/Managers/BMC', actual_managers[0].path)
class SystemCollectionTestCase(base.TestCase):

View File

@ -69,6 +69,14 @@ class UtilsTestCase(base.TestCase):
subresource_path)
self.assertEqual(expected_result, value)
def test_get_sub_resource_path_by_collection(self):
subresource_path = ["Links", "ManagedBy"]
expected_result = ['/redfish/v1/Managers/BMC']
value = utils.get_sub_resource_path_by(self.sys_inst,
subresource_path,
is_collection=True)
self.assertEqual(expected_result, value)
def test_get_sub_resource_path_by_fails(self):
subresource_path = ['Links', 'Chassis']
expected_result = 'attribute Links/Chassis/@odata.id is missing'

View File

@ -66,13 +66,17 @@ def int_or_none(x):
return int(x)
def get_sub_resource_path_by(resource, subresource_name):
def get_sub_resource_path_by(resource, subresource_name, is_collection=False):
"""Helper function to find the subresource path
:param resource: ResourceBase instance on which the name
gets queried upon.
:param subresource_name: name of the resource field to
fetch the '@odata.id' from.
:param is_collection: if `True`, expect a list of resources to
fetch the '@odata.id' from.
:returns: Resource path (if `is_collection` is `False`) or
a list of resource paths (if `is_collection` is `True`).
"""
if not subresource_name:
raise ValueError('"subresource_name" cannot be empty')
@ -88,12 +92,24 @@ def get_sub_resource_path_by(resource, subresource_name):
raise exceptions.MissingAttributeError(
attribute='/'.join(subresource_name), resource=resource.path)
if '@odata.id' not in body:
raise exceptions.MissingAttributeError(
attribute='/'.join(subresource_name) + '/@odata.id',
resource=resource.path)
elements = []
return body['@odata.id']
try:
if is_collection:
for element in body:
elements.append(element['@odata.id'])
return elements
else:
return body['@odata.id']
except (TypeError, KeyError):
attribute = '/'.join(subresource_name)
if is_collection:
attribute += '[%s]' % len(elements)
attribute += '/@odata.id'
raise exceptions.MissingAttributeError(
attribute=attribute, resource=resource.path)
def max_safe(iterable, default=0):