diff --git a/api-ref/source/containers.inc b/api-ref/source/containers.inc index 671e7fbc0..83794eef6 100644 --- a/api-ref/source/containers.inc +++ b/api-ref/source/containers.inc @@ -221,6 +221,8 @@ Delete a container Delete a container. To delete a container in `Creating` or `Running` state, request to /v1/containers/{container_ident}?force=True +To stop and delete a container, request to /v1/containers/{container +_ident}?stop=True Response Codes -------------- @@ -243,6 +245,7 @@ Request - container_ident: container_ident - force: force + - stop: stop Response -------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index e8b305463..b17e7f74a 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -89,6 +89,12 @@ stdout: in: query required: false type: boolean +stop: + description: | + Whether or not stop a container firstly before deleting it. + in: query + required: false + type: string tag: description: | The tag of the container image. diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index 4072aad7d..436853630 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -476,16 +476,39 @@ class ContainersController(base.Controller): except ValueError: msg = _('Valid force values are true, false, 0, 1, yes and no') raise exception.InvalidValue(msg) - if not force: + stop = kwargs.pop('stop', None) + compute_api = pecan.request.compute_api + if not force and stop is None: utils.validate_container_state(container, 'delete') - else: + elif force and stop is None: req_version = pecan.request.version min_version = versions.Version('', '', '', '1.7') if req_version >= min_version: policy.enforce(context, "container:delete_force", action="container:delete_force") - utils.validate_container_state(container, 'delete_force') - compute_api = pecan.request.compute_api + utils.validate_container_state(container, 'delete_force') +# Remove this line temporarily for tempest issues. +# else: +# raise exception.InvalidParamInVersion(param='force', +# req_version=req_version, +# min_version=min_version) + elif stop is not None: + req_version = pecan.request.version + min_version = versions.Version('', '', '', '1.11') + if req_version >= min_version: + if stop: + check_policy_on_container(container.as_dict(), + "container:stop") + utils.validate_container_state(container, + 'delete_after_stop') + LOG.debug('Calling compute.container_stop with %s ' + 'before delete', + container.uuid) + compute_api.container_stop(context, container, 10) + else: + raise exception.InvalidParamInVersion(param='stop', + req_version=req_version, + min_version=min_version) container.status = consts.DELETING compute_api.container_delete(context, container, force) pecan.response.status = 204 diff --git a/zun/api/controllers/v1/schemas/containers.py b/zun/api/controllers/v1/schemas/containers.py index 27a8f2f24..3a9e92673 100644 --- a/zun/api/controllers/v1/schemas/containers.py +++ b/zun/api/controllers/v1/schemas/containers.py @@ -74,7 +74,8 @@ query_param_delete = { 'type': 'object', 'properties': { 'force': parameter_types.boolean_extended, - 'all_tenants': parameter_types.boolean_extended + 'all_tenants': parameter_types.boolean_extended, + 'stop': parameter_types.boolean_extended }, 'additionalProperties': False } diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index a92c9a131..6ac4471f7 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -44,10 +44,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.9 - Add support set container's hostname * 1.10 - Make delete container async * 1.11 - Add mounts to container create + * 1.12 - Add support to stop container before delete """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.11' +CURRENT_MAX_VER = '1.12' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index 75ceb9de0..57178d41f 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -79,16 +79,19 @@ user documentation. 1.9 --- + Add a new attribute 'hostname' to the request to create a container. Users can use this attribute to specify container's hostname. 1.10 ---- + Make container delete API async. Delete operation for a container can take long time, so making it async to improve user experience. 1.11 ---- + Add a new attribute 'mounts' to the request to create a container. Users can use this attribute to specify one or multiple mounts for the container. Each mount could specify the source and destination. @@ -97,3 +100,10 @@ user documentation. For examples: [{'source': 'my-vol', 'destination': '/data'}] + +1.12 +---- + + Add a new attribute 'stop' to the request to delete containers. + Users can use this attribute to stop and delete the container without + using the --force option. diff --git a/zun/common/utils.py b/zun/common/utils.py index 19812d55e..1119a3dc5 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -48,6 +48,7 @@ VALID_STATES = { 'delete_force': [consts.CREATED, consts.CREATING, consts.ERROR, consts.RUNNING, consts.STOPPED, consts.UNKNOWN, consts.DELETED], + 'delete_after_stop': [consts.RUNNING], 'start': [consts.CREATED, consts.STOPPED, consts.ERROR], 'stop': [consts.RUNNING], 'reboot': [consts.CREATED, consts.RUNNING, consts.STOPPED, consts.ERROR], diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index 6ed6d3ae6..626ed5dbd 100644 --- a/zun/tests/unit/api/base.py +++ b/zun/tests/unit/api/base.py @@ -27,7 +27,7 @@ from zun.tests.unit.db import base PATH_PREFIX = '/v1' -CURRENT_VERSION = "container 1.11" +CURRENT_VERSION = "container 1.12" class FunctionalTest(base.DbTestCase): diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index 731dd86a4..996b94418 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest): 'default_version': {'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.11', + 'max_version': '1.12', 'min_version': '1.1', 'status': 'CURRENT'}, 'description': 'Zun is an OpenStack project which ' @@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest): 'versions': [{'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.11', + 'max_version': '1.12', 'min_version': '1.1', 'status': 'CURRENT'}]} diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index 755baca1a..bda35ccba 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -1323,6 +1323,24 @@ class TestContainerController(api_base.FunctionalTest): context = mock_container_delete.call_args[0][0] self.assertIs(True, context.all_tenants) + @patch('zun.common.utils.validate_container_state') + @patch('zun.compute.api.API.container_stop') + @patch('zun.compute.api.API.container_delete') + @patch('zun.objects.Container.get_by_uuid') + def test_delete_container_by_uuid_with_stop(self, mock_get_by_uuid, + mock_container_stop, + mock_container_delete, + mock_validate): + test_container = utils.get_test_container() + test_container_obj = objects.Container(self.context, **test_container) + mock_get_by_uuid.return_value = test_container_obj + + container_uuid = test_container.get('uuid') + response = self.delete('/v1/containers/%s?stop=True' % + container_uuid) + + self.assertEqual(204, response.status_int) + def test_delete_by_uuid_invalid_state(self): uuid = uuidutils.generate_uuid() test_object = utils.create_test_container(context=self.context,