From 542f3ced2f316fe1a569154fe31a300a108bcac9 Mon Sep 17 00:00:00 2001 From: Anusha Ramineni Date: Tue, 2 May 2017 16:43:04 +0530 Subject: [PATCH] Add Validation for Node Manage/Action API's Partially-Implements blueprint validation Change-Id: If10e37e0036c88a4bd0b183793e4344d8c257b67 --- api-ref/source/valence-api-v1-nodes.inc | 2 +- valence/api/v1/nodes.py | 4 +- valence/controller/nodes.py | 13 ---- .../tests/unit/validation/test_validation.py | 68 ++++++++++++++++--- valence/validation/schemas.py | 51 +++++++++++++- 5 files changed, 112 insertions(+), 26 deletions(-) diff --git a/api-ref/source/valence-api-v1-nodes.inc b/api-ref/source/valence-api-v1-nodes.inc index d9b3230..b38a0fd 100644 --- a/api-ref/source/valence-api-v1-nodes.inc +++ b/api-ref/source/valence-api-v1-nodes.inc @@ -332,7 +332,7 @@ Response Manage Node =========== -.. rest_method:: POST /v1/nodes/managed +.. rest_method:: POST /v1/nodes/manage Manage a composed node already existing in the RSD rack by creating a Valence database entry for it, allowing Valence to perform all operations diff --git a/valence/api/v1/nodes.py b/valence/api/v1/nodes.py index 2291d04..8f0167f 100644 --- a/valence/api/v1/nodes.py +++ b/valence/api/v1/nodes.py @@ -48,14 +48,16 @@ class Node(Resource): class NodeAction(Resource): + @validator.check_input('node_action_schema') def post(self, node_uuid): return utils.make_response( - http_client.OK, + http_client.NO_CONTENT, nodes.Node.node_action(node_uuid, request.get_json())) class NodeManage(Resource): + @validator.check_input('node_manage_schema') def post(self): return utils.make_response( http_client.OK, nodes.Node.manage_node(request.get_json())) diff --git a/valence/controller/nodes.py b/valence/controller/nodes.py index fb12a89..eac333d 100644 --- a/valence/controller/nodes.py +++ b/valence/controller/nodes.py @@ -191,19 +191,6 @@ class Node(object): param request_body: parameter of node action return: message of this deletion """ - # Get node detail from db, and map node uuid to index index = db_api.Connection.get_composed_node_by_uuid(node_uuid).index - - # TODO(lin.yang): should validate request body whether follow specifc - # format, like - # { - # "Reset": { - # "Type": "On" - # } - # } - # Should rework this part after basic validation framework for api - # input is done. - # https://review.openstack.org/#/c/422547/ - return redfish.node_action(index, request_body) diff --git a/valence/tests/unit/validation/test_validation.py b/valence/tests/unit/validation/test_validation.py index a6cfdb7..68b58d5 100644 --- a/valence/tests/unit/validation/test_validation.py +++ b/valence/tests/unit/validation/test_validation.py @@ -3,6 +3,7 @@ import json import mock from oslotest import base +from six.moves import http_client from valence.api import app as flask_app from valence.common import constants @@ -32,7 +33,7 @@ class TestFlavorApi(TestApiValidation): response = self.app.post('/v1/flavors', content_type='application/json', data=json.dumps(flavor)) - self.assertEqual(200, response.status_code) + self.assertEqual(http_client.OK, response.status_code) mock_create.assert_called_once_with(flavor) def test_flavor_create_incorrect_param(self): @@ -44,7 +45,7 @@ class TestFlavorApi(TestApiValidation): content_type='application/json', data=json.dumps(self.flavor)) response = json.loads(response.data.decode()) - self.assertEqual(400, response['status']) + self.assertEqual(http_client.BAD_REQUEST, response['status']) self.assertEqual('ValidationError', response['code']) # Test invalid key @@ -53,7 +54,7 @@ class TestFlavorApi(TestApiValidation): content_type='application/json', data=json.dumps(self.flavor)) response = json.loads(response.data.decode()) - self.assertEqual(400, response['status']) + self.assertEqual(http_client.BAD_REQUEST, response['status']) self.assertEqual('ValidationError', response['code']) @@ -84,7 +85,7 @@ class TestPodmanagerApi(TestApiValidation): response = self.app.post('/v1/pod_managers', content_type='application/json', data=json.dumps(values)) - self.assertEqual(200, response.status_code) + self.assertEqual(http_client.OK, response.status_code) def test_check_creation_incomplete_parameters(self): incomplete_values = { @@ -95,7 +96,7 @@ class TestPodmanagerApi(TestApiValidation): content_type='application/json', data=json.dumps(incomplete_values)) response = json.loads(response.data.decode()) - self.assertEqual(400, response['status']) + self.assertEqual(http_client.BAD_REQUEST, response['status']) self.assertEqual('ValidationError', response['code']) def test_check_creation_invalid_authentication(self): @@ -111,7 +112,7 @@ class TestPodmanagerApi(TestApiValidation): content_type='application/json', data=json.dumps(invalid_auth_values)) response = json.loads(response.data.decode()) - self.assertEqual(400, response['status']) + self.assertEqual(http_client.BAD_REQUEST, response['status']) self.assertEqual('ValidationError', response['code']) @@ -136,7 +137,7 @@ class TestNodeApi(TestApiValidation): resp = self.app.post('/v1/nodes', content_type='application/json', data=json.dumps(req)) - self.assertEqual(200, resp.status_code) + self.assertEqual(http_client.OK, resp.status_code) @mock.patch('valence.controller.nodes.Node.compose_node') def test_compose_request_using_flavor(self, mock_compose): @@ -148,7 +149,7 @@ class TestNodeApi(TestApiValidation): resp = self.app.post('/v1/nodes', content_type='application/json', data=json.dumps(req)) - self.assertEqual(200, resp.status_code) + self.assertEqual(http_client.OK, resp.status_code) def test_compose_request_invalid_params(self): req = { @@ -158,5 +159,54 @@ class TestNodeApi(TestApiValidation): content_type='application/json', data=json.dumps(req)) response = json.loads(resp.data.decode()) - self.assertEqual(400, response['status']) + self.assertEqual(http_client.BAD_REQUEST, response['status']) + self.assertEqual('ValidationError', response['code']) + + @mock.patch('valence.controller.nodes.Node.manage_node') + def test_node_manage_request(self, mock_manage): + req = {"node_index": "fake-index"} + mock_manage.return_value = {"uuid": "ea8e2a25-2901-438d-8157-de7ffd", + "links": "fake-links", + "name": "fake-node", + "index": "fake-index"} + resp = self.app.post('/v1/nodes/manage', + content_type='application/json', + data=json.dumps(req)) + mock_manage.assert_called_once_with(req) + self.assertEqual(http_client.OK, resp.status_code) + + def test_node_manage_request_invalid(self): + req = {"node_id": "fake-index"} + resp = self.app.post('/v1/nodes/manage', + content_type='application/json', + data=json.dumps(req)) + response = json.loads(resp.data.decode()) + self.assertEqual(http_client.BAD_REQUEST, response['status']) + self.assertEqual('ValidationError', response['code']) + + @mock.patch('valence.controller.nodes.Node.node_action') + def test_node_action_request(self, mock_action): + req = { + "Reset": { + "Type": "On" + } + } + mock_action.return_value = None + resp = self.app.post('/v1/nodes/fake-node/action', + content_type='application/json', + data=json.dumps(req)) + mock_action.assert_called_once_with('fake-node', req) + self.assertEqual(http_client.NO_CONTENT, resp.status_code) + + def test_node_action_request_invalid(self): + req = { + "Boot": { + "Type": "On" + } + } + resp = self.app.post('/v1/nodes/fake-node/action', + content_type='application/json', + data=json.dumps(req)) + response = json.loads(resp.data.decode()) + self.assertEqual(http_client.BAD_REQUEST, response['status']) self.assertEqual('ValidationError', response['code']) diff --git a/valence/validation/schemas.py b/valence/validation/schemas.py index b99de8a..7331a53 100644 --- a/valence/validation/schemas.py +++ b/valence/validation/schemas.py @@ -68,7 +68,6 @@ podmanager_schema = { 'additionalProperties': False, } - jsonschema.Draft4Validator.check_schema(podmanager_schema) compose_node_with_flavor = { @@ -91,6 +90,54 @@ compose_node_schema = { jsonschema.Draft4Validator.check_schema(compose_node_schema) +node_manage_schema = { + 'type': 'object', + 'properties': { + 'node_index': {'type': 'string'}, + }, + 'required': ['node_index'], + 'additionalProperties': False, +} + +jsonschema.Draft4Validator.check_schema(node_manage_schema) + +node_action_schema = { + 'type': 'object', + 'properties': { + 'Boot': { + 'type': 'object', + 'properties': { + 'Enabled': { + 'enum': ['Once', 'Continuous'] + }, + 'Target': { + 'enum': ['Pxe', 'Hdd', 'None'] + }, + }, + 'required': ['Enabled', 'Target'], + 'additionalProperties': False, + }, + 'Reset': { + 'type': 'object', + 'properties': { + 'Type': { + 'enum': ['On', 'ForceOn', 'ForceOff', 'GracefulRestart'] + }, + }, + 'required': ['Type'], + 'additionalProperties': False, + }, + }, + 'oneOf': [ + {'required': ['Boot']}, + {'required': ['Reset']}], + 'additionalProperties': False, +} + +jsonschema.Draft4Validator.check_schema(node_action_schema) + SCHEMAS = {'flavor_schema': flavor_schema, 'podmanager_schema': podmanager_schema, - 'compose_node_schema': compose_node_schema, } + 'compose_node_schema': compose_node_schema, + 'node_manage_schema': node_manage_schema, + 'node_action_schema': node_action_schema, }