Correct node attach/detach action info URI in RSD 2.4

In RSD 2.4, the value of "@Redfish.ActionInfo" field in node is changed
to a ActionInfo URI instead of resource structure like below shows. So
made change to RSD 2.4 module to fit it.

    "@Redfish.ActionInfo": {
        "@odata.id":"/redfish/v1/Nodes/Node1/Actions/AttachResourceActionInfo"
    }

Change-Id: I910089c1f9986c5b35ccdfea5156b59e3d97dd87
This commit is contained in:
Lin Yang 2019-03-11 11:27:33 -07:00
parent b7ed44ca2d
commit f0c456f916
10 changed files with 475 additions and 4 deletions

View File

@ -16,6 +16,7 @@
from sushy.resources import base
from rsd_lib.resources import v2_3
from rsd_lib.resources.v2_4.node import node
from rsd_lib.resources.v2_4.storage_service import storage_service
@ -45,3 +46,22 @@ class RSDLibV2_4(v2_3.RSDLibV2_3):
return storage_service.StorageService(
self._conn, identity,
redfish_version=self.redfish_version)
def get_node_collection(self):
"""Get the NodeCollection object
:raises: MissingAttributeError, if the collection attribute is
not found
:returns: a NodeCollection object
"""
return node.NodeCollection(self._conn, self._nodes_path,
redfish_version=self.redfish_version)
def get_node(self, identity):
"""Given the identity return a Node object
:param identity: The identity of the Node resource
:returns: The Node object
"""
return node.Node(self._conn, identity,
redfish_version=self.redfish_version)

View File

View File

@ -0,0 +1,57 @@
# Copyright 2018 Intel, 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.
from sushy.resources import base
from rsd_lib.resources.v2_3.node import node
class AttachEndpointActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
action_info_path = base.Field('@Redfish.ActionInfo')
action_info = None
class DetachEndpointActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
action_info_path = base.Field('@Redfish.ActionInfo')
action_info = None
class NodeActionsField(node.NodeActionsField):
attach_endpoint = AttachEndpointActionField('#ComposedNode.AttachResource')
detach_endpoint = DetachEndpointActionField('#ComposedNode.DetachResource')
class Node(node.Node):
_actions = NodeActionsField('Actions', required=True)
class NodeCollection(node.NodeCollection):
@property
def _resource_type(self):
return Node
def __init__(self, connector, path, redfish_version=None):
"""A class representing a NodeCollection
:param connector: A Connector instance
:param path: The canonical path to the Node collection
resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of the given version.
"""
super(NodeCollection, self).__init__(connector, path, redfish_version)

View File

@ -3,7 +3,7 @@
"@odata.id" : "/redfish/v1/Nodes",
"@odata.type" : "#ComposedNodeCollection.ComposedNodeCollection",
"Name" : "Composed Node Collection",
"Members@odata.count" : 5,
"Members@odata.count" : 1,
"Members" : [{
"@odata.id" : "/redfish/v1/Nodes/1"
}],

View File

@ -0,0 +1,29 @@
{
"@odata.context" : "/redfish/v1/$metadata#ActionInfo.ActionInfo",
"@odata.id" : "/redfish/v1/Nodes/2/Actions/AttachResourceActionInfo",
"@odata.type" : "#ActionInfo.v1_0_2.ActionInfo",
"Id" : "AttachResourceActionInfo",
"Name" : "Attach Resource ActionInfo",
"Description" : null,
"Parameters" : [
{
"Name" : "Resource",
"Required" : true,
"DataType" : "Object",
"ObjectDataType" : "#Resource.Resource",
"AllowableValues" : [
{
"@odata.id" : "/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1"
}
]
},
{
"Name" : "Protocol",
"Required" : false,
"DataType" : "String",
"ObjectDataType" : null,
"AllowableValues" : [ "NVMeOverFabrics" ]
}
],
"Oem" : {}
}

View File

@ -0,0 +1,110 @@
{
"@odata.context": "/redfish/v1/$metadata#Nodes/Members/$entity",
"@odata.id": "/redfish/v1/Nodes/Node1",
"@odata.type": "#ComposedNode.1.1.0.ComposedNode",
"Id": "Node1",
"Name": "Composed Node",
"Description": "Node #1",
"UUID": "fa39d108-7d70-400a-9db2-6940375c31c2",
"PowerState": "On",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
"Processors": {
"Count": 2,
"Model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"Memory": {
"TotalSystemMemoryGiB": 32,
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup" : "OK"
}
},
"ComposedNodeState": "Allocated",
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"None",
"Pxe",
"Hdd",
"RemoteDrive"
],
"BootSourceOverrideMode": "Legacy",
"BootSourceOverrideMode@Redfish.AllowableValues": ["Legacy",
"UEFI"]
},
"Oem": {},
"Links": {
"ComputerSystem": {
"@odata.id": "/redfish/v1/Systems/System1"
},
"Processors": [
{
"@odata.id": "/redfish/v1/Systems/System1/Processors/CPU1"
}
],
"Memory": [
{
"@odata.id": "/redfish/v1/Systems/System1/Memory/Dimm1"
}
],
"EthernetInterfaces": [
{
"@odata.id":
"/redfish/v1/Systems/System1/EthernetInterfaces/LAN1"
}
],
"LocalDrives": [
{
"@odata.id": "/redfish/v1/Chassis/Blade1/Drives/1"
}
],
"RemoteDrives": [
{
"@odata.id": "/redfish/v1/Services/RSS1/Targets/target1"
}
],
"ManagedBy": [
{
"@odata.id": "/redfish/v1/Managers/PODM"
}
],
"Oem": {}
},
"Actions": {
"#ComposedNode.Reset": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset",
"ResetType@Redfish.AllowableValues": [
"On",
"ForceOff",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
"GracefulShutdown"
]
},
"#ComposedNode.Assemble": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble"
},
"#ComposedNode.AttachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachResource",
"@Redfish.ActionInfo": "/redfish/v1/Nodes/Node1/Actions/AttachResourceActionInfo"
},
"#ComposedNode.DetachResource": {
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachResource",
"@Redfish.ActionInfo": "/redfish/v1/Nodes/Node1/Actions/DetachResourceActionInfo"
}
}
}

View File

@ -0,0 +1,15 @@
{
"@odata.context" : "/redfish/v1/$metadata#ComposedNodeCollection.ComposedNodeCollection",
"@odata.id" : "/redfish/v1/Nodes",
"@odata.type" : "#ComposedNodeCollection.ComposedNodeCollection",
"Name" : "Composed Node Collection",
"Members@odata.count" : 1,
"Members" : [{
"@odata.id" : "/redfish/v1/Nodes/1"
}],
"Actions" : {
"#ComposedNodeCollection.Allocate" : {
"target" : "/redfish/v1/Nodes/Actions/Allocate"
}
}
}

View File

@ -0,0 +1,240 @@
# Copyright 2017 Intel, 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 json
import mock
import testtools
from sushy import exceptions
from rsd_lib.resources.v2_4.node import node
from rsd_lib.tests.unit.fakes import request_fakes
class NodeTestCase(testtools.TestCase):
def setUp(self):
super(NodeTestCase, self).setUp()
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/v2_4/node.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst = node.Node(
self.conn, '/redfish/v1/Nodes/Node1',
redfish_version='1.0.2')
def test__get_attach_endpoint_action_element(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
value = self.node_inst._get_attach_endpoint_action_element()
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'ComposedNode.AttachResource',
value.target_uri)
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'AttachResourceActionInfo',
value.action_info_path)
expected = [
{
"name": "Resource",
"required": True,
"data_type": "Object",
"object_data_type": "#Resource.Resource",
"allowable_values": (
"/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1",
)
},
{
"name": "Protocol",
"required": False,
"data_type": "String",
"object_data_type": None,
"allowable_values": ["NVMeOverFabrics"]
}
]
self.assertEqual(expected, value.action_info.parameters)
def test_get_allowed_attach_endpoints(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
expected = self.node_inst.get_allowed_attach_endpoints()
result = ("/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1",)
self.assertEqual(expected, result)
def test_attach_endpoint(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst.attach_endpoint(
resource='/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1',
protocol='NVMeOverFabrics')
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachResource',
data={'Resource': {'@odata.id': '/redfish/v1/StorageServices'
'/1-sv-1/Volumes/1-sv-1-vl-1'},
'Protocol': 'NVMeOverFabrics'})
def test_attach_endpoint_only_with_resource_uri(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst.attach_endpoint(
resource='/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1')
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachResource',
data={'Resource': {'@odata.id': '/redfish/v1/StorageServices'
'/1-sv-1/Volumes/1-sv-1-vl-1'}})
def test_attach_endpoint_invalid_parameter(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'"resource" value.*{0}'.format(
'/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1')):
self.node_inst.attach_endpoint(resource='invalid-resource')
def test__get_detach_endpoint_action_element(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
value = self.node_inst._get_detach_endpoint_action_element()
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'ComposedNode.DetachResource',
value.target_uri)
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'DetachResourceActionInfo',
value.action_info_path)
expected = [
{
"name": "Resource",
"required": True,
"data_type": "Object",
"object_data_type": "#Resource.Resource",
"allowable_values": (
"/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1",
)
},
{
"name": "Protocol",
"required": False,
"data_type": "String",
"object_data_type": None,
"allowable_values": ["NVMeOverFabrics"]
}
]
self.assertEqual(expected, value.action_info.parameters)
def test_get_allowed_detach_endpoints(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
expected = self.node_inst.get_allowed_detach_endpoints()
result = ("/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1",)
self.assertEqual(expected, result)
def test_detach_endpoint(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst.detach_endpoint(
resource='/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1')
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachResource',
data={'Resource': {'@odata.id': '/redfish/v1/StorageServices'
'/1-sv-1/Volumes/1-sv-1-vl-1'}})
def test_detach_endpoint_invalid_parameter(self):
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'"resource" value.*{0}'.format(
'/redfish/v1/StorageServices/1-sv-1/Volumes/1-sv-1-vl-1')):
self.node_inst.detach_endpoint(resource='invalid-resource')
def test_refresh(self):
self.assertIsNone(self.node_inst._actions.attach_endpoint.action_info)
self.assertIsNone(self.node_inst._actions.detach_endpoint.action_info)
with open('rsd_lib/tests/unit/json_samples/v2_4/'
'attach_action_info.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst._get_attach_endpoint_action_element()
self.node_inst._get_detach_endpoint_action_element()
self.assertIsNotNone(
self.node_inst._actions.attach_endpoint.action_info)
self.assertIsNotNone(
self.node_inst._actions.detach_endpoint.action_info)
with open('rsd_lib/tests/unit/json_samples/v2_4/node.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst.refresh()
self.assertIsNone(self.node_inst._actions.attach_endpoint.action_info)
self.assertIsNone(self.node_inst._actions.detach_endpoint.action_info)
class NodeCollectionTestCase(testtools.TestCase):
def setUp(self):
super(NodeCollectionTestCase, self).setUp()
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/v2_4/node_collection.json',
'r') as f:
self.conn.get.return_value = request_fakes.fake_request_get(
json.loads(f.read()))
self.node_col = node.NodeCollection(
self.conn, '/redfish/v1/Nodes', redfish_version='1.0.2')
def test__parse_attributes(self):
self.node_col._parse_attributes()
self.assertEqual('1.0.2', self.node_col.redfish_version)
self.assertEqual('Composed Node Collection', self.node_col.name)
self.assertEqual(('/redfish/v1/Nodes/1',),
self.node_col.members_identities)
@mock.patch.object(node, 'Node', autospec=True)
def test_get_member(self, mock_node):
self.node_col.get_member('/redfish/v1/Nodes/1')
mock_node.assert_called_once_with(
self.node_col._conn, '/redfish/v1/Nodes/1',
redfish_version=self.node_col.redfish_version)
@mock.patch.object(node, 'Node', autospec=True)
def test_get_members(self, mock_node):
members = self.node_col.get_members()
mock_node.assert_called_once_with(
self.node_col._conn, '/redfish/v1/Nodes/1',
redfish_version=self.node_col.redfish_version)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))

View File

@ -28,9 +28,9 @@ from rsd_lib.resources.v2_3.ethernet_switch import ethernet_switch \
as v2_3_ethernet_switch
from rsd_lib.resources.v2_3.fabric import fabric as v2_3_fabric
from rsd_lib.resources.v2_3.manager import manager as v2_3_manager
from rsd_lib.resources.v2_3.node import node as v2_3_node
from rsd_lib.resources.v2_3.system import system as v2_3_system
from rsd_lib.resources import v2_4
from rsd_lib.resources.v2_4.node import node as v2_4_node
from rsd_lib.resources.v2_4.storage_service import storage_service \
as v2_4_storage_service
@ -82,14 +82,14 @@ class RSDLibV2_3TestCase(testtools.TestCase):
self.rsd._conn, 'fake-system-id',
redfish_version=self.rsd.redfish_version)
@mock.patch.object(v2_3_node, 'NodeCollection', autospec=True)
@mock.patch.object(v2_4_node, 'NodeCollection', autospec=True)
def test_get_node_collection(self, mock_node_collection):
self.rsd.get_node_collection()
mock_node_collection.assert_called_once_with(
self.rsd._conn, '/redfish/v1/Nodes',
redfish_version=self.rsd.redfish_version)
@mock.patch.object(v2_3_node, 'Node', autospec=True)
@mock.patch.object(v2_4_node, 'Node', autospec=True)
def test_get_node(self, mock_node):
self.rsd.get_node('fake-node-id')
mock_node.assert_called_once_with(