From be919e10a51f96422c705782c029926b0b34f235 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Mon, 13 Feb 2017 16:26:45 -0800 Subject: [PATCH] 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 --- ...est.json => node-reset-state-request.json} | 0 .../mockup/node-set-boot-soruce-request.json | 6 ++ api-ref/source/parameters.yaml | 15 +++- api-ref/source/valence-api-v1-nodes.inc | 15 +++- valence/redfish/redfish.py | 56 +++++++++++++- valence/tests/unit/redfish/test_redfish.py | 74 +++++++++++++++++++ 6 files changed, 157 insertions(+), 9 deletions(-) rename api-ref/source/mockup/{node-post-action-request.json => node-reset-state-request.json} (100%) create mode 100644 api-ref/source/mockup/node-set-boot-soruce-request.json diff --git a/api-ref/source/mockup/node-post-action-request.json b/api-ref/source/mockup/node-reset-state-request.json similarity index 100% rename from api-ref/source/mockup/node-post-action-request.json rename to api-ref/source/mockup/node-reset-state-request.json diff --git a/api-ref/source/mockup/node-set-boot-soruce-request.json b/api-ref/source/mockup/node-set-boot-soruce-request.json new file mode 100644 index 0000000..a5a96ab --- /dev/null +++ b/api-ref/source/mockup/node-set-boot-soruce-request.json @@ -0,0 +1,6 @@ +{ + "Boot": { + "Enabled": "Once", + "Target": "Pxe" + } +} diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 50d1949..a63ccd3 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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. diff --git a/api-ref/source/valence-api-v1-nodes.inc b/api-ref/source/valence-api-v1-nodes.inc index e5c2787..3cf0657 100644 --- a/api-ref/source/valence-api-v1-nodes.inc +++ b/api-ref/source/valence-api-v1-nodes.inc @@ -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 diff --git a/valence/redfish/redfish.py b/valence/redfish/redfish.py index f1fcb97..6a34a6b 100644 --- a/valence/redfish/redfish.py +++ b/valence/redfish/redfish.py @@ -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( diff --git a/valence/tests/unit/redfish/test_redfish.py b/valence/tests/unit/redfish/test_redfish.py index aae0f2b..38e5bfc 100644 --- a/valence/tests/unit/redfish/test_redfish.py +++ b/valence/tests/unit/redfish/test_redfish.py @@ -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"""