From 2359f9b644b903b27cc1de906b59607e4920bd95 Mon Sep 17 00:00:00 2001 From: Pierre Padrixe Date: Thu, 5 Jun 2014 18:07:50 +0200 Subject: [PATCH] Add Plan versioning management * Version can now be passed in attribute of the Plan YAML File. Version attribute is a mandatory field. * According to the version specified, the appropriate WSME Plan and Plan Handler are created and called. * Plan validation is done by the WSME framework. * How to add a new Plan version (e.g with version 2) ? - Add your Plan_v2 WSME object in datamodels - Add your Plan_v2_handler in handlers - Add init_plan_v2 method in Plan controller. Change-Id: I7fcc6e11391b9f026768a4afafaaa638c43ec303 Closes-Bug: #1291114 --- examples/plans/ex1.yaml | 1 + examples/plans/ghost.yaml | 1 + functionaltests/api/v1/public/test_trigger.py | 3 +- functionaltests/api/v1/test_assembly.py | 3 +- functionaltests/api/v1/test_component.py | 3 +- functionaltests/api/v1/test_plan.py | 6 ++-- solum/api/controllers/v1/plan.py | 26 ++++++++++++--- solum/tests/api/v1/test_plan.py | 33 +++++++++++++++---- 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/examples/plans/ex1.yaml b/examples/plans/ex1.yaml index ed42b2274..2523919dc 100644 --- a/examples/plans/ex1.yaml +++ b/examples/plans/ex1.yaml @@ -1,3 +1,4 @@ +version: 1 name: ex1 description: Nodejs express. artifacts: diff --git a/examples/plans/ghost.yaml b/examples/plans/ghost.yaml index 6844f6478..5939853cb 100644 --- a/examples/plans/ghost.yaml +++ b/examples/plans/ghost.yaml @@ -1,3 +1,4 @@ +version: 1 name: ghost description: ghost blogging platform artifacts: diff --git a/functionaltests/api/v1/public/test_trigger.py b/functionaltests/api/v1/public/test_trigger.py index 7c1e167ee..087a8d5fa 100644 --- a/functionaltests/api/v1/public/test_trigger.py +++ b/functionaltests/api/v1/public/test_trigger.py @@ -19,7 +19,8 @@ from functionaltests.api import base assembly_data = {'name': 'test_assembly', 'description': 'desc assembly'} -plan_data = {'name': 'test_plan', +plan_data = {'version': '1', + 'name': 'test_plan', 'description': 'A test to create plan'} diff --git a/functionaltests/api/v1/test_assembly.py b/functionaltests/api/v1/test_assembly.py index 3a0e0366f..5d9933360 100644 --- a/functionaltests/api/v1/test_assembly.py +++ b/functionaltests/api/v1/test_assembly.py @@ -27,7 +27,8 @@ sample_data = {"name": "test_assembly", "status": "status", "application_uri": "http://localhost:5000"} -plan_sample_data = {"name": "test_plan", +plan_sample_data = {"version": "1", + "name": "test_plan", "description": "A test to create plan", "project_id": "project_id", "user_id": "user_id"} diff --git a/functionaltests/api/v1/test_component.py b/functionaltests/api/v1/test_component.py index 30a1f4423..a8b464485 100644 --- a/functionaltests/api/v1/test_component.py +++ b/functionaltests/api/v1/test_component.py @@ -26,7 +26,8 @@ sample_data = {'name': 'test_service', assembly_sample_data = {'name': 'test_assembly', 'description': 'desc assembly'} -plan_sample_data = {'name': 'test_plan', +plan_sample_data = {'version': '1', + 'name': 'test_plan', 'description': 'A test to create plan'} diff --git a/functionaltests/api/v1/test_plan.py b/functionaltests/api/v1/test_plan.py index 43ec9c8db..b9a58ace7 100644 --- a/functionaltests/api/v1/test_plan.py +++ b/functionaltests/api/v1/test_plan.py @@ -17,7 +17,8 @@ import yaml from functionaltests.api import base -sample_data = {"name": "test_plan", +sample_data = {"version": "1", + "name": "test_plan", "description": "A test to create plan", "artifacts": [{ "name": "No_deus", @@ -110,7 +111,8 @@ class TestPlanController(base.TestCase): def test_plans_put(self): uuid = self._create_plan() - updated_data = {"name": "test_plan_updated", + updated_data = {"version": "1", + "name": "test_plan_updated", "description": "A test to create plan updated", "type": "plan", "artifacts": []} diff --git a/solum/api/controllers/v1/plan.py b/solum/api/controllers/v1/plan.py index 264578979..c9ff34831 100644 --- a/solum/api/controllers/v1/plan.py +++ b/solum/api/controllers/v1/plan.py @@ -14,6 +14,7 @@ import pecan from pecan import rest +import sys import wsmeext.pecan as wsme_pecan import yaml @@ -24,6 +25,25 @@ from solum.common import yamlutils from solum import objects +def init_plan_v1(yml_input_plan): + plan_handler_v1 = plan_handler.PlanHandler( + pecan.request.security_context) + plan_v1 = plan.Plan(**yml_input_plan) + return plan_handler_v1, plan_v1 + + +def init_plan_by_version(yml_input_plan): + version = yml_input_plan.get('version') + if version is None: + raise exception.BadRequest( + reason='Version attribute is missing in Plan') + mod = sys.modules[__name__] + if not hasattr(mod, 'init_plan_v%s' % version): + raise exception.BadRequest(reason='Plan version %s is invalid.' + % version) + return getattr(mod, 'init_plan_v%s' % version)(yml_input_plan) + + class PlanController(rest.RestController): """Manages operations on a single plan.""" @@ -44,14 +64,13 @@ class PlanController(rest.RestController): @pecan.expose(content_type='application/x-yaml') def put(self): """Modify this plan.""" - handler = plan_handler.PlanHandler(pecan.request.security_context) if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest try: yml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: raise exception.BadRequest('Plan is invalid. ' + excp.message) - data = plan.Plan(**yml_input_plan) + handler, data = init_plan_by_version(yml_input_plan) updated_plan_yml = yaml.dump(handler.update( self._id, data.as_dict(objects.registry.Plan)).refined_content()) pecan.response.status = 200 @@ -78,14 +97,13 @@ class PlansController(rest.RestController): @pecan.expose(content_type='application/x-yaml') def post(self): """Create a new plan.""" - handler = plan_handler.PlanHandler(pecan.request.security_context) if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest try: yml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: raise exception.BadRequest('Plan is invalid. ' + excp.message) - data = plan.Plan(**yml_input_plan) + handler, data = init_plan_by_version(yml_input_plan) create_plan_yml = yaml.dump(handler.create( data.as_dict(objects.registry.Plan)).refined_content()) pecan.response.status = 201 diff --git a/solum/tests/api/v1/test_plan.py b/solum/tests/api/v1/test_plan.py index c6b9b020d..99c5465da 100644 --- a/solum/tests/api/v1/test_plan.py +++ b/solum/tests/api/v1/test_plan.py @@ -60,26 +60,38 @@ class TestPlanController(base.BaseTestCase): self.assertEqual(400, resp_mock.status) def test_plan_put_not_found(self, PlanHandler, resp_mock, request_mock): - data = 'name: ex_plan1\ndescription: yaml plan1.' + data = 'version: 1\nname: ex_plan1\ndescription: dsc1.' request_mock.body = data request_mock.content_type = 'application/x-yaml' hand_update = PlanHandler.return_value.update hand_update.side_effect = exception.ResourceNotFound( name='plan', plan_id='test_id') plan.PlanController('test_id').put() - hand_update.assert_called_with('test_id', yaml.load(data)) + hand_update.assert_called_with('test_id', {'name': 'ex_plan1', + 'description': u'dsc1.'}) self.assertEqual(404, resp_mock.status) def test_plan_put_ok(self, PlanHandler, resp_mock, request_mock): - data = 'name: ex_plan1\ndescription: yaml plan1.' + data = 'version: 1\nname: ex_plan1\ndescription: dsc1.' request_mock.body = data request_mock.content_type = 'application/x-yaml' hand_update = PlanHandler.return_value.update hand_update.return_value = fakes.FakePlan() plan.PlanController('test_id').put() - hand_update.assert_called_with('test_id', yaml.load(data)) + hand_update.assert_called_with('test_id', {'name': 'ex_plan1', + 'description': u'dsc1.'}) self.assertEqual(200, resp_mock.status) + def test_plan_put_version_not_found(self, PlanHandler, + resp_mock, request_mock): + data = 'name: ex_plan1\ndescription: yaml plan1.\nversion: 2' + request_mock.body = data + request_mock.content_type = 'application/x-yaml' + hand_update = PlanHandler.return_value.update + hand_update.return_value = fakes.FakePlan() + plan.PlanController('test_id').put() + self.assertEqual(400, resp_mock.status) + def test_plan_delete_not_found(self, PlanHandler, resp_mock, request_mock): hand_delete = PlanHandler.return_value.delete hand_delete.side_effect = exception.ResourceNotFound( @@ -118,15 +130,24 @@ class TestPlansController(base.BaseTestCase): hand_get.assert_called_with() def test_plans_post(self, PlanHandler, resp_mock, request_mock): - request_mock.body = 'name: ex_plan1\ndescription: yaml plan1.' + request_mock.body = 'version: 1\nname: ex_plan1\ndescription: dsc1.' request_mock.content_type = 'application/x-yaml' hand_create = PlanHandler.return_value.create hand_create.return_value = fakes.FakePlan() plan.PlansController().post() hand_create.assert_called_with({'name': 'ex_plan1', - 'description': 'yaml plan1.'}) + 'description': 'dsc1.'}) self.assertEqual(201, resp_mock.status) + def test_plans_post_version_not_found(self, PlanHandler, + resp_mock, request_mock): + request_mock.body = 'version: 2\nname: ex_plan1\ndescription: dsc1.' + request_mock.content_type = 'application/x-yaml' + hand_create = PlanHandler.return_value.create + hand_create.return_value = fakes.FakePlan() + plan.PlansController().post() + self.assertEqual(400, resp_mock.status) + def test_plans_post_nodata(self, handler_mock, resp_mock, request_mock): request_mock.body = '' request_mock.content_type = 'application/json'