Add node composition functionality

Add the ability to compose and delete nodes to the node
collection class.

Change-Id: I8eabb15ce0f164807ef8f08c3e67fb25b32fb2ac
Implements-Blueprint: node-composition
This commit is contained in:
Nate Potter 2017-08-03 12:59:32 -07:00
parent 834e77e6d6
commit 7bb501c0f1
4 changed files with 114 additions and 3 deletions

View File

@ -45,13 +45,21 @@ class DetachEndpointActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
class ActionsField(base.CompositeField):
class ComposeNodeActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
class NodeActionsField(base.CompositeField):
reset = common.ResetActionField('#ComposedNode.Reset')
assemble = AssembleActionField('#ComposedNode.Assemble')
attach_endpoint = AttachEndpointActionField('#ComposedNode.AttachEndpoint')
detach_endpoint = DetachEndpointActionField('#ComposedNode.DetachEndpoint')
class NodeCollectionActionsField(base.CompositeField):
compose = ComposeNodeActionField('#ComposedNodeCollection.Allocate')
class BootField(base.CompositeField):
allowed_values = base.Field(
'BootSourceOverrideTarget@Redfish.AllowableValues',
@ -112,7 +120,7 @@ class Node(base.ResourceBase):
_processors = None # ref to ProcessorCollection instance
_actions = ActionsField('Actions', required=True)
_actions = NodeActionsField('Actions', required=True)
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ComposedNode
@ -248,6 +256,16 @@ class Node(base.ResourceBase):
return self._processors
def delete_node(self):
"""Delete (disassemble) the node.
When this action is called several tasks are performed. A graceful
shutdown is sent to the computer system, all VLANs except reserved ones
are removed from associated ethernet switch ports, the computer system
is deallocated and the remote target is deallocated.
"""
self._conn.delete(self.path)
def refresh(self):
super(Node, self).refresh()
self._processors = None
@ -255,6 +273,8 @@ class Node(base.ResourceBase):
class NodeCollection(base.ResourceCollectionBase):
_actions = NodeCollectionActionsField('Actions', required=True)
@property
def _resource_type(self):
return Node
@ -269,3 +289,22 @@ class NodeCollection(base.ResourceCollectionBase):
"""
super(NodeCollection, self).__init__(connector, path,
redfish_version)
def _get_compose_action_element(self):
compose_action = self._actions.compose
if not compose_action:
raise exceptions.MissingActionError(
action='#ComposedNodeCollection.Allocate',
resource=self._path)
return compose_action
def compose_node(self, properties={}):
"""Compose a node from RackScale hardware
:param properties: The properties requested for node composition
:returns: The location of the composed node
"""
target_uri = self._get_compose_action_element().target_uri
resp = self._conn.post(target_uri, data=properties)
LOG.info("Node created at %s", resp.headers['Location'])
return resp.headers['Location']

View File

View File

@ -0,0 +1,39 @@
# 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.
def fake_request_get(json_data):
class MockResponse(object):
def __init__(self, json_data):
self.json_data = json_data
def json(self):
return self.json_data
return MockResponse(json_data)
def fake_request_post(json_data, headers=None):
class MockResponse(object):
def __init__(self, json_data, headers):
self.json_data = json_data
self.headers = headers
def json(self):
return self.json_data
return MockResponse(json_data, headers)

View File

@ -22,6 +22,7 @@ import testtools
from rsd_lib.resources.node import constants as node_cons
from rsd_lib.resources.node import node
from rsd_lib.tests.unit.fakes import request_fakes
class NodeTestCase(testtools.TestCase):
@ -311,6 +312,10 @@ class NodeTestCase(testtools.TestCase):
self.node_inst.processors.summary)
self.conn.get.return_value.json.assert_not_called()
def test_delete_node(self):
self.node_inst.delete_node()
self.node_inst._conn.delete.assert_called_once()
class NodeCollectionTestCase(testtools.TestCase):
@ -319,7 +324,10 @@ class NodeCollectionTestCase(testtools.TestCase):
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/node_collection.json',
'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.conn.get.return_value = request_fakes.fake_request_get(
json.loads(f.read()))
self.conn.post.return_value = request_fakes.fake_request_post(
None, headers={"Location": "Test"})
self.node_col = node.NodeCollection(
self.conn, '/redfish/v1/Nodes', redfish_version='1.0.2')
@ -345,3 +353,28 @@ class NodeCollectionTestCase(testtools.TestCase):
redfish_version=self.node_col.redfish_version)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))
def test__get_compose_action_element(self):
value = self.node_col._get_compose_action_element()
self.assertEqual('/redfish/v1/Nodes/Actions/Allocate',
value.target_uri)
def test_compose_node_no_properties(self):
self.node_col.compose_node()
self.node_col._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Actions/Allocate', data={})
def test_compose_node_properties(self):
props = {
'Name': 'test',
'Description': 'this is a test node',
'Processors': [{
'TotalCores': 2
}],
'Memory': [{
'CapacityMiB': 16000
}]
}
self.node_col.compose_node(properties=props)
self.node_col._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Actions/Allocate', data=props)