From 6e35fb4b577b1a86f4a0c57d15c94add957d0a21 Mon Sep 17 00:00:00 2001 From: visitor Date: Wed, 1 Jun 2016 13:18:17 +0200 Subject: [PATCH] Fix application template update Updating an application/service in a environment template does not work and return 404 error. This patchs solves that bug. Change-Id: I03f51c45512c4282ef99ddc1ed9ba55460827a94 Closes-Bug: #1587833 --- doc/source/specification/murano-env-temp.rst | 80 +++++++++++++++++++ murano/api/v1/router.py | 4 + murano/api/v1/template_applications.py | 7 +- murano/common/utils.py | 69 +++++++++++++++- murano/db/services/core_services.py | 33 ++++++++ .../tests/unit/api/v1/test_env_templates.py | 29 ++++++- .../application_catalog_client.py | 8 ++ .../application_catalog/test_env_templates.py | 16 ++++ ...-app-in-env-template-08d92b22bd1355f5.yaml | 3 + 9 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/update-app-in-env-template-08d92b22bd1355f5.yaml diff --git a/doc/source/specification/murano-env-temp.rst b/doc/source/specification/murano-env-temp.rst index 7c822ea3f..8ea8b3ec1 100644 --- a/doc/source/specification/murano-env-temp.rst +++ b/doc/source/specification/murano-env-temp.rst @@ -414,6 +414,86 @@ Get applications information from an environment template | 404 | The environment template does not exist | +----------------+-----------------------------------------------------------+ +Update applications information from an environment template +------------------------------------------------------------ + +*Request* + ++----------+-----------------------------------------------+-----------------------------------+ +| Method | URI | Description | ++==========+===============================================+===================================+ +| PUT | /templates/{env-temp-id}/services/{service-id}| It updates the service description| ++----------+-----------------------------------------------+-----------------------------------+ + +*Parameters:* + +* `env-temp-id` - The environment template ID, required +* `service-id` - The service ID to be updated +* payload - the service description + +*Content-Type* + application/json + +*Example* + +:: + + { + "instance": { + "assignFloatingIp": "true", + "keyname": "mykeyname", + "image": "cloud-fedora-v3", + "flavor": "m1.medium", + "?": { + "type": "io.murano.resources.LinuxMuranoInstance", + "id": "ef984a74-29a4-45c0-b1dc-2ab9f075732e" + } + }, + "name": "orion", + "port": "8080", + "?": { + "type": "io.murano.apps.apache.Tomcat", + "id": "54cea43d-5970-4c73-b9ac-fea656f3c722" + } + } + +*Response* + +:: + + + { + "instance": + { + "assignFloatingIp": "true", + "keyname": "mykeyname", + "image": "cloud-fedora-v3", + "flavor": "m1.medium", + "?": + { + "type": "io.murano.resources.LinuxMuranoInstance", + "id": "ef984a74-29a4-45c0-b1dc-2ab9f075732e" + } + }, + "name": "orion", + "?": + { + "type": "io.murano.apps.apache.Tomcat", + "id": "54cea43d-5970-4c73-b9ac-fea656f3c722" + }, + "port": "8080" + } + ++----------------+-----------------------------------------------------------+ +| Code | Description | ++================+===========================================================+ +| 200 | OK. Environment Template updated successfully | ++----------------+-----------------------------------------------------------+ +| 401 | User is not authorized to access this session | ++----------------+-----------------------------------------------------------+ +| 404 | The environment template does not exist | ++----------------+-----------------------------------------------------------+ + Create an environment from an environment template -------------------------------------------------- diff --git a/murano/api/v1/router.py b/murano/api/v1/router.py index cce2315d5..396456991 100644 --- a/murano/api/v1/router.py +++ b/murano/api/v1/router.py @@ -139,6 +139,10 @@ class API(wsgi.Router): controller=applications_resource, action='post', conditions={'method': ['POST']}, path='') + mapper.connect('/templates/{env_template_id}/services/{path:.*?}', + controller=applications_resource, + action='put', + conditions={'method': ['PUT']}, path='') mapper.connect('/templates/{env_template_id}/services/{path:.*?}', controller=applications_resource, action='delete', diff --git a/murano/api/v1/template_applications.py b/murano/api/v1/template_applications.py index 983fe63ca..c75036dc3 100644 --- a/murano/api/v1/template_applications.py +++ b/murano/api/v1/template_applications.py @@ -21,6 +21,7 @@ from webob import exc from murano.api.v1 import request_statistics from murano.common.helpers import token_sanitizer from murano.common.i18n import _ +from murano.common import policy from murano.common import wsgi from murano.db.services import core_services from murano import utils @@ -144,16 +145,16 @@ class Controller(object): :param body: the information about the service :return: the service description updated. """ + policy.check('update_service_env_template', request.context) LOG.debug('Applications:Put '.format(templ_id=env_template_id, body=body, path=path)) - put_data = core_services.CoreServices.put_data - session_id = request.context.session + put_data = core_services.CoreServices.put_application_data try: - result = put_data(env_template_id, session_id, body, path) + result = put_data(env_template_id, body, path) except (KeyError, ValueError): msg = _('The template does not exist {templ_id}').format( templ_id=env_template_id) diff --git a/murano/common/utils.py b/murano/common/utils.py index fe175e9f9..cf799655c 100644 --- a/murano/common/utils.py +++ b/murano/common/utils.py @@ -91,7 +91,10 @@ class TraverseHelper(object): parent_path = '/'.join(path.split('/')[:-1]) node = TraverseHelper.get(parent_path, source) key = path[1:].split('/')[-1] - node[key] = value + if is_number(key): + node[int(key)] = value + else: + node[key] = value @staticmethod def insert(path, value, source): @@ -139,6 +142,70 @@ class TraverseHelper(object): raise ValueError(_('Source object or path is malformed')) +def is_number(var): + try: + int(var) + return True + except Exception: + return False + + +def is_different(obj1, obj2): + """Stripped-down version of deep.diff comparator + + Compares arbitrary nested objects, handles circular links, but doesn't + point to the first difference as deep.diff does. + """ + class Difference(Exception): + pass + + def is_in(o, st): + for _o in st: + if o is _o: + return True + return False + + def rec(o1, o2, stack1=(), stack2=()): + if is_in(o1, stack1) and is_in(o2, stack2): + # circular reference detected - break the loop + return + elif is_in(o1, stack1): + raise Difference() + else: + stack1 += (o1,) + stack2 += (o2,) + + if o1 is o2: + return + elif (isinstance(o1, six.string_types) and + isinstance(o2, six.string_types)) and o1 == o2: + return + elif type(o1) != type(o2): + raise Difference() + elif isinstance(o1, dict): + # check for keys inequality + rec(o1.keys(), o2.keys(), stack1, stack2) + for key in o1.keys(): + rec(o1[key], o2[key], stack1, stack2) + elif isinstance(o1, (list, tuple, set)): + if len(o1) != len(o2): + raise Difference() + else: + for _o1, _o2 in zip(o1, o2): + rec(_o1, _o2, stack1, stack2) + elif hasattr(o1, '__dict__'): + return rec(o1.__dict__, o2.__dict__, stack1, stack2) + elif o1 != o2: + raise Difference() + + try: + rec(obj1, obj2) + except Difference: + return True + else: + return False + + def build_entity_map(value): def build_entity_map_recursive(value, id_map): if isinstance(value, dict): diff --git a/murano/db/services/core_services.py b/murano/db/services/core_services.py index 95bc5ff39..b76ff5929 100644 --- a/murano/db/services/core_services.py +++ b/murano/db/services/core_services.py @@ -229,3 +229,36 @@ class CoreServices(object): utils.TraverseHelper.remove(path, tmp_description) save_description(tmp_description, env_template_id) return tmp_description + + @staticmethod + def put_application_data(env_template_id, data, path): + """It stores the application data inside the template description. + + :param env_template_id: The env_template_id to obtain the data + :param data: the template description + :param path: Id of service for which we checking status. + :return: The template description + """ + get_description = env_temp.EnvTemplateServices.get_description + save_description = env_temp.EnvTemplateServices.save_description + + temp_description = get_description(env_template_id) + if temp_description is None: + msg = _('Environment Template is not found').format( + env_template_id) + LOG.error(msg) + raise exc.HTTPNotFound(explanation=msg) + + count = 0 + id = path[1:].split('/')[-1] + for service in temp_description["services"]: + if service["?"]["id"]: + if service["?"]["id"] == id: + break + count+1 + + utils.TraverseHelper.update("services/{0}".format(count), + data, temp_description) + temp_description['updated'] = str(timeutils.utcnow()) + save_description(temp_description, env_template_id) + return data diff --git a/murano/tests/unit/api/v1/test_env_templates.py b/murano/tests/unit/api/v1/test_env_templates.py index f7eed4951..d36fcd48f 100644 --- a/murano/tests/unit/api/v1/test_env_templates.py +++ b/murano/tests/unit/api/v1/test_env_templates.py @@ -668,13 +668,35 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): self.assertEqual(self.uuids[4], body_returned['session_id']) self.assertEqual(self.uuids[3], body_returned['environment_id']) + def test_update_service_in_template(self): + """Test the service is updated in the environment template""" + self.fixture = self.useFixture(config_fixture.Config()) + self.fixture.conf(args=[]) + self._set_policy_rules( + {'create_env_template': '@', + 'update_service_env_template': '@'} + ) + updated_env = "UPDATED_ENV" + env_template = self._create_env_template_services() + self.expect_policy_check('update_service_env_template') + env_template["name"] = updated_env + + req = self._put('/templates/{0}/services/{1}'. + format(self.uuids[0], "service_id"), + jsonutils.dump_as_bytes(env_template)) + result = req.get_response(self.api) + self.assertIsNotNone(result) + self.assertEqual(200, result.status_code) + body_returned = jsonutils.loads(result.body) + self.assertEqual(updated_env, body_returned['name']) + def test_mallformed_env_body(self): """Check that an illegal temp name results in an HTTPClientError.""" self._set_policy_rules( {'create_env_template': '@', 'create_environment': '@'} ) - self. _create_env_template_no_service() + self._create_env_template_no_service() self.expect_policy_check('create_environment', {'env_template_id': self.uuids[0]}) @@ -770,11 +792,12 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): "port": "8080", "?": { "type": "io.murano.apps.apache.Tomcat", - "id": "54cea43d-5970-4c73-b9ac-fea656f3c722" + "id": "service_id" } } ] } req = self._post('/templates', jsonutils.dump_as_bytes(body)) - req.get_response(self.api) + result = req.get_response(self.api) + return result.json diff --git a/murano_tempest_tests/services/application_catalog/application_catalog_client.py b/murano_tempest_tests/services/application_catalog/application_catalog_client.py index 0e73e3211..5e563d45b 100644 --- a/murano_tempest_tests/services/application_catalog/application_catalog_client.py +++ b/murano_tempest_tests/services/application_catalog/application_catalog_client.py @@ -331,6 +331,14 @@ class ApplicationCatalogClient(rest_client.RestClient): self.expected_success(200, resp.status) return json.loads(body) + def update_service_from_env_template(self, env_template_id, service_id, + post_body): + uri = 'v1/templates/{0}/services/{1}'.format(env_template_id, + service_id) + resp, body = self.put(uri, json.dumps(post_body)) + self.expected_success(200, resp.status) + return self._parse_resp(body) + def delete_service_from_env_template(self, env_template_name, service_id): uri = 'v1/templates/{0}/services/{1}'.format(env_template_name, service_id) diff --git a/murano_tempest_tests/tests/api/application_catalog/test_env_templates.py b/murano_tempest_tests/tests/api/application_catalog/test_env_templates.py index 1e3943246..e4090b3ec 100644 --- a/murano_tempest_tests/tests/api/application_catalog/test_env_templates.py +++ b/murano_tempest_tests/tests/api/application_catalog/test_env_templates.py @@ -98,6 +98,22 @@ class TestEnvironmentTemplates(base.BaseApplicationCatalogTest): get_services_list_in_env_template(self.env_template['id']) self.assertNotIn(service, services) + @testtools.testcase.attr('smoke') + def test_update_service_in_env_templates(self): + env_template_services = self.application_catalog_client.\ + get_services_list_in_env_template(self.env_template['id']) + self.assertIsInstance(env_template_services, list) + post_body = self._get_demo_app() + service = self.application_catalog_client.\ + create_service_in_env_template(self.env_template['id'], post_body) + self.assertEqual(post_body['name'], service['name']) + post_body["name"] = "updated_name" + service = self.application_catalog_client.\ + update_service_from_env_template(self.env_template['id'], + service["?"]["id"], + post_body) + self.assertEqual("updated_name", service['name']) + @testtools.testcase.attr('smoke') def test_create_public_env_template(self): name = utils.generate_name('create_public_env_template') diff --git a/releasenotes/notes/update-app-in-env-template-08d92b22bd1355f5.yaml b/releasenotes/notes/update-app-in-env-template-08d92b22bd1355f5.yaml new file mode 100644 index 000000000..a9a0fd754 --- /dev/null +++ b/releasenotes/notes/update-app-in-env-template-08d92b22bd1355f5.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add application template update endpoint