From 594b3e27f929c13ac8d65132d0246e61b415a06d Mon Sep 17 00:00:00 2001 From: Michal Gershenzon Date: Tue, 17 May 2016 13:35:07 +0000 Subject: [PATCH] Add API to validate ad-hoc action Add an API that gets an ad-hoc action DSL and validates it. This is done in the same way workflows are validated today. Change-Id: Ibbb949ef38befae1ef83a2a56cda4c817ceb41d4 Implements: blueprint validate-ad-hoc-action-api --- doc/source/developer/webapi/v2.rst | 7 +- mistral/api/controllers/v2/action.py | 5 ++ mistral/tests/unit/api/v2/test_actions.py | 88 +++++++++++++++++++ ...hoc-action-api-added-6d7eaaedbe8129a7.yaml | 3 + 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/validate-ad-hoc-action-api-added-6d7eaaedbe8129a7.yaml diff --git a/doc/source/developer/webapi/v2.rst b/doc/source/developer/webapi/v2.rst index 15d050493..5cabace0f 100644 --- a/doc/source/developer/webapi/v2.rst +++ b/doc/source/developer/webapi/v2.rst @@ -190,7 +190,7 @@ There are three service groups according to Mistral architecture currently, name Validation ---------- -Validation endpoints allow to check correctness of workbook and workflow DSL without having to upload them into Mistral. +Validation endpoints allow to check correctness of workbook, workflow and ad-hoc action DSL without having to upload them into Mistral. **POST /v2/workbooks/validation** Validate workbook content (DSL grammar and semantics). @@ -198,4 +198,7 @@ Validation endpoints allow to check correctness of workbook and workflow DSL wit **POST /v2/workflows/validation** Validate workflow content (DSL grammar and semantics). -These endpoints expect workbook or workflow text (DSL) correspondingly in a request body. +**POST /v2/actions/validation** + Validate ad-hoc action content (DSL grammar and semantics). + +These endpoints expect workbook, workflow or ad-hoc action text (DSL) correspondingly in a request body. diff --git a/mistral/api/controllers/v2/action.py b/mistral/api/controllers/v2/action.py index 66490bced..c4ad56f79 100644 --- a/mistral/api/controllers/v2/action.py +++ b/mistral/api/controllers/v2/action.py @@ -22,11 +22,13 @@ import wsmeext.pecan as wsme_pecan from mistral.api.controllers import resource from mistral.api.controllers.v2 import types +from mistral.api.controllers.v2 import validation from mistral.api.hooks import content_type as ct_hook from mistral.db.v2 import api as db_api from mistral import exceptions as exc from mistral.services import actions from mistral.utils import rest_utils +from mistral.workbook import parser as spec_parser LOG = logging.getLogger(__name__) SCOPE_TYPES = wtypes.Enum(str, 'private', 'public') @@ -93,6 +95,9 @@ class ActionsController(rest.RestController, hooks.HookController): # delete ContentTypeHook. __hooks__ = [ct_hook.ContentTypeHook("application/json", ['POST', 'PUT'])] + validate = validation.SpecValidationController( + spec_parser.get_action_list_spec_from_yaml) + @rest_utils.wrap_wsme_controller_exception @wsme_pecan.wsexpose(Action, wtypes.text) def get(self, name): diff --git a/mistral/tests/unit/api/v2/test_actions.py b/mistral/tests/unit/api/v2/test_actions.py index cefaccc6c..7b0724536 100644 --- a/mistral/tests/unit/api/v2/test_actions.py +++ b/mistral/tests/unit/api/v2/test_actions.py @@ -36,6 +36,35 @@ my_action: output: "{$.str1}{$.str2}" """ +ACTION_DEFINITION_INVALID_NO_BASE = """ +--- +version: '2.0' + +my_action: + description: My super cool action. + tags: ['test', 'v2'] + + base-input: + output: "{$.str1}{$.str2}" +""" + +ACTION_DEFINITION_INVALID_YAQL = """ +--- +version: '2.0' + +my_action: + description: My super cool action. + tags: ['test', 'v2'] + base: std.echo + base-input: + output: <% $. %> +""" + +ACTION_DSL_PARSE_EXCEPTION = """ +--- +% +""" + SYSTEM_ACTION_DEFINITION = """ --- version: '2.0' @@ -339,3 +368,62 @@ class TestActionsController(base.APITest): self.assertEqual(400, resp.status_int) self.assertIn("Unknown sort direction", resp.body.decode()) + + def test_validate(self): + resp = self.app.post( + '/v2/actions/validate', + ACTION_DEFINITION, + headers={'Content-Type': 'text/plain'} + ) + + self.assertEqual(200, resp.status_int) + self.assertTrue(resp.json['valid']) + + def test_validate_invalid_model_exception(self): + resp = self.app.post( + '/v2/actions/validate', + ACTION_DEFINITION_INVALID_NO_BASE, + headers={'Content-Type': 'text/plain'}, + expect_errors=True + ) + + self.assertEqual(200, resp.status_int) + self.assertFalse(resp.json['valid']) + self.assertIn("Invalid DSL", resp.json['error']) + + def test_validate_dsl_parse_exception(self): + resp = self.app.post( + '/v2/actions/validate', + ACTION_DSL_PARSE_EXCEPTION, + headers={'Content-Type': 'text/plain'}, + expect_errors=True + ) + + self.assertEqual(200, resp.status_int) + self.assertFalse(resp.json['valid']) + self.assertIn("Definition could not be parsed", resp.json['error']) + + def test_validate_yaql_parse_exception(self): + resp = self.app.post( + '/v2/actions/validate', + ACTION_DEFINITION_INVALID_YAQL, + headers={'Content-Type': 'text/plain'}, + expect_errors=True + ) + + self.assertEqual(200, resp.status_int) + self.assertFalse(resp.json['valid']) + self.assertIn("unexpected end of statement", + resp.json['error']) + + def test_validate_empty(self): + resp = self.app.post( + '/v2/actions/validate', + '', + headers={'Content-Type': 'text/plain'}, + expect_errors=True + ) + + self.assertEqual(200, resp.status_int) + self.assertFalse(resp.json['valid']) + self.assertIn("Invalid DSL", resp.json['error']) diff --git a/releasenotes/notes/validate-ad-hoc-action-api-added-6d7eaaedbe8129a7.yaml b/releasenotes/notes/validate-ad-hoc-action-api-added-6d7eaaedbe8129a7.yaml new file mode 100644 index 000000000..b8e79048e --- /dev/null +++ b/releasenotes/notes/validate-ad-hoc-action-api-added-6d7eaaedbe8129a7.yaml @@ -0,0 +1,3 @@ +--- +features: + - New API for validating ad-hoc actions was added. \ No newline at end of file