diff --git a/doc/source/specification/murano-api.rst b/doc/source/specification/murano-api.rst index 3449f52b..fc6b6a34 100644 --- a/doc/source/specification/murano-api.rst +++ b/doc/source/specification/murano-api.rst @@ -716,6 +716,93 @@ Created application returned | 400 | Required header or body are not provided | +----------------+-----------------------------------------------------------+ +Update applications +------------------- + +Applications list for environment can be updated. + +*Request* + +**Content-Type** + application/json + ++----------------+-----------------------------------------------------------+------------------------------------+ +| Method | URI | Header | ++================+===========================================================+====================================+ +| PUT | /environments//services | X-Configuration-Session | ++----------------+-----------------------------------------------------------+------------------------------------+ + +:: + + [{ + "instance": { + "availabilityZone": "nova", + "name": "apache-instance", + "assignFloatingIp": true, + "keyname": "", + "flavor": "m1.small", + "image": "146d5523-7b2d-4abc-b0d0-2041f920ce26", + "?": { + "type": "io.murano.resources.LinuxMuranoInstance", + "id": "25185cb6f29b415fa2e438309827a797" + } + }, + "name": "ApacheHttpServer", + "enablePHP": true, + "?": { + "type": "com.example.apache.ApacheHttpServer", + "id": "6e66106d7dcb4748a5c570150a3df80f" + } + }] + + +*Response* + +Updated applications list returned + + +**Content-Type** + application/json + +:: + + [{ + "instance": { + "availabilityZone": "nova", + "name": "apache-instance", + "assignFloatingIp": true, + "keyname": "", + "flavor": "m1.small", + "image": "146d5523-7b2d-4abc-b0d0-2041f920ce26", + "?": { + "type": "io.murano.resources.LinuxMuranoInstance", + "id": "25185cb6f29b415fa2e438309827a797" + } + }, + "name": "ApacheHttpServer", + "enablePHP": true, + "?": { + "type": "com.example.apache.ApacheHttpServer", + "id": "6e66106d7dcb4748a5c570150a3df80f" + } + }] + ++----------------+-----------------------------------------------------------+ +| Code | Description | ++================+===========================================================+ +| 200 | Services are updated successfully | ++----------------+-----------------------------------------------------------+ +| 400 | Required header is not provided | ++----------------+-----------------------------------------------------------+ +| 401 | User is not authorized | ++----------------+-----------------------------------------------------------+ +| 403 | Session is in deploying state and could not be updated | +| | or user is not allowed to update services | ++----------------+-----------------------------------------------------------+ +| 404 | Not found. Specified environment and/or session do not | +| | exist | ++----------------+-----------------------------------------------------------+ + Delete application from environment ----------------------------------- diff --git a/murano/api/v1/services.py b/murano/api/v1/services.py index 5db9d034..7089e1e5 100644 --- a/murano/api/v1/services.py +++ b/murano/api/v1/services.py @@ -92,10 +92,7 @@ class Controller(object): @normalize_path def put(self, request, environment_id, path, body=None): if not body: - msg = _('Request body is empty: please, provide ' - 'application object model') - LOG.error(msg) - raise exc.HTTPBadRequest(msg) + body = [] LOG.debug('Services:Put '.format(environment_id, body, path)) diff --git a/murano/tests/unit/api/v1/test_services.py b/murano/tests/unit/api/v1/test_services.py index f91ad1c4..ef97295f 100644 --- a/murano/tests/unit/api/v1/test_services.py +++ b/murano/tests/unit/api/v1/test_services.py @@ -110,8 +110,10 @@ class TestServicesApi(tb.ControllerTest, tb.MuranoApiTestCase): request.headers['X-Configuration-Session'] = str(session_id) request.context.session = session_id - self.assertRaises(exc.HTTPBadRequest, self.services_controller.put, - request, environment_id, path) + # Check that empty body can be put + response = self.services_controller.put(request, environment_id, + path, []) + self.assertEqual([], response) response = self.services_controller.put(request, environment_id, path, "test service") 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 7876229e..bb83d8ed 100644 --- a/murano_tempest_tests/services/application_catalog/application_catalog_client.py +++ b/murano_tempest_tests/services/application_catalog/application_catalog_client.py @@ -196,6 +196,19 @@ class ApplicationCatalogClient(rest_client.RestClient): self.expected_success(200, resp.status) return self._parse_resp(body) + def update_services(self, environment_id, session_id, put_body=None): + headers = self.get_headers() + headers.update( + {'X-Configuration-Session': session_id} + ) + uri = 'v1/environments/{0}/services'.format(environment_id) + resp, body = self.put(uri, json.dumps(put_body), headers) + self.expected_success(200, resp.status) + # TODO(freerunner): Need to replace json.loads() to _parse_resp + # method, when fix for https://bugs.launchpad.net/tempest/+bug/1539927 + # will resolved and new version of tempest-lib released. + return json.loads(body) + def delete_service(self, environment_id, session_id, service_id): headers = self.get_headers() headers.update( diff --git a/murano_tempest_tests/tests/api/application_catalog/test_services.py b/murano_tempest_tests/tests/api/application_catalog/test_services.py index b46f7d65..37b384ad 100644 --- a/murano_tempest_tests/tests/api/application_catalog/test_services.py +++ b/murano_tempest_tests/tests/api/application_catalog/test_services.py @@ -65,6 +65,44 @@ class TestServices(base.BaseApplicationCatalogTest): get_services_list(self.environment['id'], session['id']) self.assertEqual(len(services_list), len(services_list_)) + @testtools.testcase.attr('smoke') + def test_update_services_via_put(self): + session = self.application_catalog_client.\ + create_session(self.environment['id']) + self.addCleanup(self.application_catalog_client.delete_session, + self.environment['id'], session['id']) + put_body = [self._get_demo_app()] + self.application_catalog_client.\ + update_services(self.environment['id'], session['id'], put_body) + services_list = self.application_catalog_client.\ + get_services_list(self.environment['id'], session['id']) + self.assertEqual(1, len(services_list)) + + @testtools.testcase.attr('smoke') + def test_clear_services_via_put(self): + session = self.application_catalog_client.\ + create_session(self.environment['id']) + self.addCleanup(self.application_catalog_client.delete_session, + self.environment['id'], session['id']) + services_list = self.application_catalog_client.\ + get_services_list(self.environment['id'], session['id']) + post_body = self._get_demo_app() + self.application_catalog_client.\ + create_service(self.environment['id'], session['id'], post_body) + services_list_ = self.application_catalog_client.\ + get_services_list(self.environment['id'], session['id']) + self.assertEqual(len(services_list) + 1, len(services_list_)) + self.application_catalog_client.\ + update_services(self.environment['id'], session['id']) + services_list_ = self.application_catalog_client.\ + get_services_list(self.environment['id'], session['id']) + self.assertEqual(0, len(services_list_)) + self.application_catalog_client.\ + create_service(self.environment['id'], session['id'], post_body) + services_list_ = self.application_catalog_client.\ + get_services_list(self.environment['id'], session['id']) + self.assertEqual(1, len(services_list_)) + @testtools.testcase.attr('smoke') def test_get_service(self): session = self.application_catalog_client.\ diff --git a/murano_tempest_tests/tests/api/application_catalog/test_services_negative.py b/murano_tempest_tests/tests/api/application_catalog/test_services_negative.py index 62324230..62b8a48f 100644 --- a/murano_tempest_tests/tests/api/application_catalog/test_services_negative.py +++ b/murano_tempest_tests/tests/api/application_catalog/test_services_negative.py @@ -159,6 +159,32 @@ class TestServicesNegative(base.BaseApplicationCatalogTest): session['id'], service['?']['id']) + @testtools.testcase.attr('negative') + def test_put_services_without_env_id(self): + session = self.application_catalog_client.\ + create_session(self.environment['id']) + self.addCleanup(self.application_catalog_client.delete_session, + self.environment['id'], session['id']) + put_body = [self._get_demo_app()] + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.update_services, + None, + session['id'], + put_body) + + @testtools.testcase.attr('negative') + def test_put_services_without_sess_id(self): + session = self.application_catalog_client.\ + create_session(self.environment['id']) + self.addCleanup(self.application_catalog_client.delete_session, + self.environment['id'], session['id']) + put_body = [self._get_demo_app()] + self.assertRaises(exceptions.BadRequest, + self.application_catalog_client.update_services, + self.environment['id'], + "", + put_body) + class TestServicesNegativeTenantIsolation(base.BaseApplicationCatalogTest): diff --git a/releasenotes/notes/put-empty-body-d605c2083b239f76.yaml b/releasenotes/notes/put-empty-body-d605c2083b239f76.yaml new file mode 100644 index 00000000..efc452c3 --- /dev/null +++ b/releasenotes/notes/put-empty-body-d605c2083b239f76.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - It is now possible to make a PUT request with body equal to '[]' to + '/environments//services' endpoint. This will result in removing + all apps from current session. This allows deleting the last application + from environment from CLI.