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:
parent
5fbc00ed91
commit
0bf7cbf14d
|
@ -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.
|
|
@ -188,6 +188,25 @@ class Manager(base.ResourceBase):
|
||||||
self._conn, utils.get_sub_resource_path_by(self, 'VirtualMedia'),
|
self._conn, utils.get_sub_resource_path_by(self, 'VirtualMedia'),
|
||||||
redfish_version=self.redfish_version)
|
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):
|
class ManagerCollection(base.ResourceCollectionBase):
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import logging
|
||||||
from sushy import exceptions
|
from sushy import exceptions
|
||||||
from sushy.resources import base
|
from sushy.resources import base
|
||||||
from sushy.resources import common
|
from sushy.resources import common
|
||||||
|
from sushy.resources.manager import manager
|
||||||
from sushy.resources import mappings as res_maps
|
from sushy.resources import mappings as res_maps
|
||||||
from sushy.resources.system import bios
|
from sushy.resources.system import bios
|
||||||
from sushy.resources.system import constants as sys_cons
|
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"),
|
self._conn, utils.get_sub_resource_path_by(self, "Storage"),
|
||||||
redfish_version=self.redfish_version)
|
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):
|
class SystemCollection(base.ResourceCollectionBase):
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import sushy
|
||||||
from sushy import exceptions
|
from sushy import exceptions
|
||||||
from sushy.resources.manager import manager
|
from sushy.resources.manager import manager
|
||||||
from sushy.resources.manager import virtual_media
|
from sushy.resources.manager import virtual_media
|
||||||
|
from sushy.resources.system import system
|
||||||
from sushy.tests.unit import base
|
from sushy.tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
|
@ -265,6 +266,18 @@ class ManagerTestCase(base.TestCase):
|
||||||
virtual_media.VirtualMediaCollection)
|
virtual_media.VirtualMediaCollection)
|
||||||
self.assertFalse(vrt_media._is_stale)
|
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):
|
class ManagerCollectionTestCase(base.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import mock
|
||||||
import sushy
|
import sushy
|
||||||
from sushy import exceptions
|
from sushy import exceptions
|
||||||
from sushy.resources import constants as res_cons
|
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 bios
|
||||||
from sushy.resources.system import mappings as sys_map
|
from sushy.resources.system import mappings as sys_map
|
||||||
from sushy.resources.system import processor
|
from sushy.resources.system import processor
|
||||||
|
@ -478,6 +479,18 @@ class SystemTestCase(base.TestCase):
|
||||||
# | WHEN & THEN |
|
# | WHEN & THEN |
|
||||||
self.assertIsInstance(self.sys_inst.storage, storage.StorageCollection)
|
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):
|
class SystemCollectionTestCase(base.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,14 @@ class UtilsTestCase(base.TestCase):
|
||||||
subresource_path)
|
subresource_path)
|
||||||
self.assertEqual(expected_result, value)
|
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):
|
def test_get_sub_resource_path_by_fails(self):
|
||||||
subresource_path = ['Links', 'Chassis']
|
subresource_path = ['Links', 'Chassis']
|
||||||
expected_result = 'attribute Links/Chassis/@odata.id is missing'
|
expected_result = 'attribute Links/Chassis/@odata.id is missing'
|
||||||
|
|
|
@ -66,13 +66,17 @@ def int_or_none(x):
|
||||||
return int(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
|
"""Helper function to find the subresource path
|
||||||
|
|
||||||
:param resource: ResourceBase instance on which the name
|
:param resource: ResourceBase instance on which the name
|
||||||
gets queried upon.
|
gets queried upon.
|
||||||
:param subresource_name: name of the resource field to
|
:param subresource_name: name of the resource field to
|
||||||
fetch the '@odata.id' from.
|
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:
|
if not subresource_name:
|
||||||
raise ValueError('"subresource_name" cannot be empty')
|
raise ValueError('"subresource_name" cannot be empty')
|
||||||
|
@ -88,12 +92,24 @@ def get_sub_resource_path_by(resource, subresource_name):
|
||||||
raise exceptions.MissingAttributeError(
|
raise exceptions.MissingAttributeError(
|
||||||
attribute='/'.join(subresource_name), resource=resource.path)
|
attribute='/'.join(subresource_name), resource=resource.path)
|
||||||
|
|
||||||
if '@odata.id' not in body:
|
elements = []
|
||||||
raise exceptions.MissingAttributeError(
|
|
||||||
attribute='/'.join(subresource_name) + '/@odata.id',
|
|
||||||
resource=resource.path)
|
|
||||||
|
|
||||||
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):
|
def max_safe(iterable, default=0):
|
||||||
|
|
Loading…
Reference in New Issue