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:
parent
6bc8f689b0
commit
be919e10a5
6
api-ref/source/mockup/node-set-boot-soruce-request.json
Normal file
6
api-ref/source/mockup/node-set-boot-soruce-request.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Boot": {
|
||||||
|
"Enabled": "Once",
|
||||||
|
"Target": "Pxe"
|
||||||
|
}
|
||||||
|
}
|
@ -111,10 +111,17 @@ node_attach_type:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
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:
|
node_boot_source:
|
||||||
description: |
|
description: |
|
||||||
The value of this contain the source to boot the composed node, including
|
The value of this contain the source to boot the composed node, including
|
||||||
"pxe", "hdd", "none"
|
"Pxe", "Hdd", "None".
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@ -168,7 +175,7 @@ node_power_state:
|
|||||||
"On", "Off", "PoweringOn" ,"PoweringOff"
|
"On", "Off", "PoweringOn" ,"PoweringOff"
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
node_processor_asset:
|
node_processor_asset:
|
||||||
description: |
|
description: |
|
||||||
Processor asset (Object) info.
|
Processor asset (Object) info.
|
||||||
@ -208,11 +215,11 @@ node_reset_type:
|
|||||||
type: string
|
type: string
|
||||||
node_state:
|
node_state:
|
||||||
description: |
|
description: |
|
||||||
The current composed node state including
|
The current composed node state including
|
||||||
"Assembling", "Allocating", "Assembling" ,"Failed", "Assembled"
|
"Assembling", "Allocating", "Assembling" ,"Failed", "Assembled"
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
node_storage_asset:
|
node_storage_asset:
|
||||||
description: |
|
description: |
|
||||||
Storage asset (Object) info.
|
Storage asset (Object) info.
|
||||||
|
@ -212,12 +212,12 @@ Request
|
|||||||
- node_ident: node_ident
|
- node_ident: node_ident
|
||||||
|
|
||||||
|
|
||||||
Node reset
|
Node action
|
||||||
===========
|
===========
|
||||||
|
|
||||||
.. rest_method:: POST /v1/nodes/{node_ident}/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
|
Normal response codes: 204
|
||||||
@ -231,10 +231,17 @@ Request
|
|||||||
|
|
||||||
- node_ident: node_ident
|
- node_ident: node_ident
|
||||||
- reset_type: node_reset_type
|
- 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
|
:language: javascript
|
||||||
|
|
||||||
Response
|
Response
|
||||||
|
@ -575,6 +575,59 @@ def reset_node(nodeid, request):
|
|||||||
"successfully.".format(action_type))
|
"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):
|
def node_action(nodeid, request):
|
||||||
# Only support one action in single request
|
# Only support one action in single request
|
||||||
if len(list(request.keys())) != 1:
|
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
|
# Because valence assemble node by default when compose node, so only need
|
||||||
# to support "Reset" action here. In case podm new version support more
|
# to support "Reset" action here. In case podm new version support more
|
||||||
# actions, use "functions" dict to drive the workflow.
|
# 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:
|
if action not in functions:
|
||||||
raise exception.BadRequest(
|
raise exception.BadRequest(
|
||||||
|
@ -471,6 +471,80 @@ class TestRedfish(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(expected, result)
|
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')
|
@mock.patch('valence.redfish.redfish.reset_node')
|
||||||
def test_node_action_malformed_request(self, mock_reset_node):
|
def test_node_action_malformed_request(self, mock_reset_node):
|
||||||
"""Test post node_action with malformed request"""
|
"""Test post node_action with malformed request"""
|
||||||
|
Loading…
Reference in New Issue
Block a user