Support set boot source of composed node

This patch allow user to set boot source of composed node. Two
parameters are needed in request body,
"Target" - the boot source to be used at next boot
"Enabled" - options "Once" and "Continuous" are supported, which
indicate whether this action is only one time change

Partially-Implements blueprint node-action

Change-Id: I46342c884e3cb8f00069e7e2c10c878582a2b185
This commit is contained in:
Lin Yang 2017-02-13 16:26:45 -08:00
parent 6bc8f689b0
commit be919e10a5
6 changed files with 157 additions and 9 deletions

View File

@ -0,0 +1,6 @@
{
"Boot": {
"Enabled": "Once",
"Target": "Pxe"
}
}

View File

@ -111,10 +111,17 @@ node_attach_type:
in: body
required: true
type: string
node_boot_enabled:
description: |
The value of this describe the state of boot source override feature,
including "Once", "Continuous".
in: body
required: true
type: string
node_boot_source:
description: |
The value of this contain the source to boot the composed node, including
"pxe", "hdd", "none"
"Pxe", "Hdd", "None".
in: body
required: true
type: string
@ -168,7 +175,7 @@ node_power_state:
"On", "Off", "PoweringOn" ,"PoweringOff"
in: body
required: true
type: string
type: string
node_processor_asset:
description: |
Processor asset (Object) info.
@ -208,11 +215,11 @@ node_reset_type:
type: string
node_state:
description: |
The current composed node state including
The current composed node state including
"Assembling", "Allocating", "Assembling" ,"Failed", "Assembled"
in: body
required: true
type: string
type: string
node_storage_asset:
description: |
Storage asset (Object) info.

View File

@ -212,12 +212,12 @@ Request
- node_ident: node_ident
Node reset
Node action
===========
.. rest_method:: POST /v1/nodes/{node_ident}/action
Send a POST (reset) cmd to a composed node.
Send a POST cmd to a composed node, includes reset node state and set boot source.
Normal response codes: 204
@ -231,10 +231,17 @@ Request
- node_ident: node_ident
- reset_type: node_reset_type
- boot_enabled: node_boot_enabled
- boot_source: node_boot_source
**Example POST action cmd for composed node :**
**Example reset state for composed node :**
.. literalinclude:: mockup/node-post-action-request.json
.. literalinclude:: mockup/node-reset-state-request.json
:language: javascript
**Example set boot source for composed node :**
.. literalinclude:: mockup/node-set-boot-source-request.json
:language: javascript
Response

View File

@ -575,6 +575,59 @@ def reset_node(nodeid, request):
"successfully.".format(action_type))
def set_boot_source(nodeid, request):
nodes_url = get_base_resource_url("Nodes")
node_url = os.path.normpath("/".join([nodes_url, nodeid]))
resp = send_request(node_url)
if resp.status_code != http_client.OK:
# Raise exception if don't find node
raise exception.RedfishException(resp.json(),
status_code=resp.status_code)
node = resp.json()
boot_enabled = request.get("Boot", {}).get("Enabled")
boot_target = request.get("Boot", {}).get("Target")
allowable_boot_target = \
node["Boot"]["BootSourceOverrideTarget@Redfish.AllowableValues"]
if not boot_enabled or not boot_target:
raise exception.BadRequest(
detail="The content of set boot source request is malformed. "
"Please refer to Valence api specification to correct it.")
if boot_enabled not in ["Disabled", "Once", "Continuous"]:
raise exception.BadRequest(
detail="The parameter Enabled '{0}' is not in allowable list "
"['Disabled', 'Once', 'Continuous'].".format(
boot_enabled))
if allowable_boot_target and \
boot_target not in allowable_boot_target:
raise exception.BadRequest(
detail="The parameter Target '{0}' is not in allowable list "
"{1}.".format(boot_target,
allowable_boot_target))
action_resp = send_request(
node_url, 'PATCH', headers={'Content-type': 'application/json'},
json={"Boot": {"BootSourceOverrideEnabled": boot_enabled,
"BootSourceOverrideTarget": boot_target}})
if action_resp.status_code != http_client.NO_CONTENT:
raise exception.RedfishException(action_resp.json(),
status_code=action_resp.status_code)
else:
# Set boot source successfully
LOG.debug("Set boot source of composed node {0} to '{1}' with enabled "
"state '{2}' successfully."
.format(nodes_url, boot_target, boot_enabled))
return exception.confirmation(
confirm_code="Set Boot Source of Composed Node",
confirm_detail="The boot source of composed node has been set to "
"'{0}' with enabled state '{1}' successfully."
.format(boot_target, boot_enabled))
def node_action(nodeid, request):
# Only support one action in single request
if len(list(request.keys())) != 1:
@ -589,7 +642,8 @@ def node_action(nodeid, request):
# Because valence assemble node by default when compose node, so only need
# to support "Reset" action here. In case podm new version support more
# actions, use "functions" dict to drive the workflow.
functions = {"Reset": reset_node}
functions = {"Reset": reset_node,
"Boot": set_boot_source}
if action not in functions:
raise exception.BadRequest(

View File

@ -471,6 +471,80 @@ class TestRedfish(TestCase):
self.assertEqual(expected, result)
@mock.patch('valence.redfish.redfish.send_request')
@mock.patch('valence.redfish.redfish.get_base_resource_url')
def test_set_boot_source_wrong_request(self, mock_get_url, mock_request):
"""Test reset node with wrong action type"""
mock_get_url.return_value = '/redfish/v1/Nodes'
mock_request.return_value = fakes.mock_request_get(
fakes.fake_node_detail(), http_client.OK)
# Test no "Target" parameter
with self.assertRaises(exception.BadRequest) as context:
redfish.set_boot_source("1", {"Boot": {"Enabled": "Once"}})
self.assertTrue("The content of set boot source request is malformed. "
"Please refer to Valence api specification to correct "
"it." in str(context.exception.detail))
# Test no "Enabled" parameter
with self.assertRaises(exception.BadRequest) as context:
redfish.set_boot_source("1", {"Boot": {"Target": "Hdd"}})
self.assertTrue("The content of set boot source request is malformed. "
"Please refer to Valence api specification to correct "
"it." in str(context.exception.detail))
# Test no "Enabled" either "Target" parameter
with self.assertRaises(exception.BadRequest) as context:
redfish.set_boot_source("1", {"Boot": {}})
self.assertTrue("The content of set boot source request is malformed. "
"Please refer to Valence api specification to correct "
"it." in str(context.exception.detail))
# Test wrong "Enabled" parameter
with self.assertRaises(exception.BadRequest) as context:
redfish.set_boot_source("1", {"Boot": {"Enabled": "wrong_input",
"Target": "Hdd"}})
self.assertTrue("The parameter Enabled 'wrong_input' is not in "
"allowable list ['Disabled', 'Once', 'Continuous']."
in str(context.exception.detail))
# Test wrong "Enabled" parameter
with self.assertRaises(exception.BadRequest) as context:
redfish.set_boot_source("1", {"Boot": {"Enabled": "Once",
"Target": "wrong_input"}})
allowable_boot_target = \
(fakes.fake_node_detail()["Boot"]
["BootSourceOverrideTarget@Redfish.AllowableValues"])
self.assertTrue("The parameter Target 'wrong_input' is not in "
"allowable list {0}.".format(allowable_boot_target)
in str(context.exception.detail))
@mock.patch('valence.redfish.redfish.send_request')
@mock.patch('valence.redfish.redfish.get_base_resource_url')
def test_set_boot_source_success(self, mock_get_url, mock_request):
"""Test successfully reset node status"""
mock_get_url.return_value = '/redfish/v1/Nodes'
fake_node_detail = fakes.mock_request_get(
fakes.fake_node_detail(), http_client.OK)
fake_node_action_resp = fakes.mock_request_get(
{}, http_client.NO_CONTENT)
mock_request.side_effect = [fake_node_detail, fake_node_action_resp]
result = redfish.set_boot_source(
"1", {"Boot": {"Enabled": "Once", "Target": "Hdd"}})
expected = exception.confirmation(
confirm_code="Set Boot Source of Composed Node",
confirm_detail="The boot source of composed node has been set to "
"'{0}' with enabled state '{1}' successfully."
.format("Hdd", "Once"))
self.assertEqual(expected, result)
@mock.patch('valence.redfish.redfish.reset_node')
def test_node_action_malformed_request(self, mock_reset_node):
"""Test post node_action with malformed request"""