From d0a7940981bb123bfc12d4ed2d24a4bf50e513e4 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sat, 26 Jan 2019 23:50:22 +0000 Subject: [PATCH] Consolidate Container and Capsule in compute In before, create/delete Capsule has its own RPC api and compute manager implementation. The code is largely duplicated with the Container equivalent. In fact, the code duplication leads to bugs or missing features and it is hard to maintain. This commit refactor the compute node implementation for capsule. First, the capsule RPC API is removed and the controller will use the container RPC for create/delete capsule in compute node. Second, the capsule implementation is removed from compute manager. Instead, we will reuse the container implementation for capsule. Third, we introduce capsule operation in container driver. The capsule-specific logic will be implemented by different drivers. After this patch, all existing container features (i.e. resource tracking and claiming, asynchronized delete, etc.) will be available for capsule immediately. In long term, the common implementation for capsule and container will be easier to maintain. Closes-Bug: #1801649 Closes-Bug: #1777273 Partial-Bug: #1762902 Partial-Bug: #1751193 Partial-Bug: #1748825 Change-Id: Ie1d806738fcd945a4f370bdfc7fa8fb5fb815e8d --- .zuul.yaml | 2 +- zun/api/controllers/v1/capsules.py | 13 +- zun/api/controllers/versions.py | 3 +- zun/api/rest_api_version_history.rst | 7 + zun/compute/api.py | 26 ---- zun/compute/manager.py | 144 +++--------------- zun/compute/rpcapi.py | 2 +- zun/container/docker/driver.py | 117 ++++++++++++++ zun/container/driver.py | 6 + zun/tests/unit/api/base.py | 2 +- zun/tests/unit/api/controllers/test_root.py | 4 +- .../unit/api/controllers/v1/test_capsules.py | 55 +++---- zun/tests/unit/compute/test_compute_api.py | 8 - 13 files changed, 187 insertions(+), 202 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 129901b86..e46cae7c9 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -25,7 +25,7 @@ test-config: $TEMPEST_CONFIG: container_service: - min_microversion: 1.27 + min_microversion: 1.32 devstack_services: tempest: true devstack_plugins: diff --git a/zun/api/controllers/v1/capsules.py b/zun/api/controllers/v1/capsules.py index 6061b3353..bbb087748 100644 --- a/zun/api/controllers/v1/capsules.py +++ b/zun/api/controllers/v1/capsules.py @@ -13,7 +13,6 @@ # under the License. from oslo_log import log as logging - import pecan import six @@ -252,8 +251,13 @@ class CapsuleController(base.Controller): new_capsule.cpu = capsule_need_cpu new_capsule.memory = str(capsule_need_memory) new_capsule.save(context) - compute_api.capsule_create(context, new_capsule, requested_networks, - requested_volumes, extra_spec) + + kwargs = {} + kwargs['extra_spec'] = extra_spec + kwargs['requested_networks'] = requested_networks + kwargs['requested_volumes'] = requested_volumes + kwargs['run'] = True + compute_api.container_create(context, new_capsule, **kwargs) # Set the HTTP Location Header pecan.response.location = link.build_url('capsules', new_capsule.uuid) @@ -291,7 +295,8 @@ class CapsuleController(base.Controller): compute_api = pecan.request.compute_api capsule.task_state = consts.CONTAINER_DELETING capsule.save(context) - compute_api.capsule_delete(context, capsule) + compute_api.container_stop(context, capsule, 10) + compute_api.container_delete(context, capsule) pecan.response.status = 204 def _generate_name_for_capsule_container(self, new_capsule): diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index f4d5e88e3..3bcfbb1a6 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -64,10 +64,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.29 - Add enable_cpu_pinning to compute_node * 1.30 - Introduce API resource for representing private registry * 1.31 - Add 'registry_id' to containers + * 1.32 - Make capsule deletion asynchronized """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.31' +CURRENT_MAX_VER = '1.32' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index 33fe6b4f1..9f53e3792 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -241,3 +241,10 @@ user documentation. Add 'registry_id' to container resource. This attribute indicate the registry from which the container pulls images. + +1.32 +---- + + Make capsule deletion asynchronized. + API request to delete a capsule will return without waiting for the + capsule to be deleted. diff --git a/zun/compute/api.py b/zun/compute/api.py index bdaa5ccd9..1e04cc183 100644 --- a/zun/compute/api.py +++ b/zun/compute/api.py @@ -208,32 +208,6 @@ class API(object): return self.rpcapi.image_search(context, image, image_driver, exact_match, *args) - def capsule_create(self, context, new_capsule, requested_networks, - requested_volumes, extra_spec): - try: - host_state = self._schedule_container(context, new_capsule, - extra_spec) - except exception.NoValidHost: - new_capsule.status = consts.ERROR - new_capsule.status_reason = _( - "There are not enough hosts available.") - new_capsule.save(context) - return - except Exception: - new_capsule.status = consts.ERROR - new_capsule.status_reason = _("Unexpected exception occurred.") - new_capsule.save(context) - raise - for container in new_capsule.containers: - self._record_action_start(context, container, - container_actions.CREATE) - self.rpcapi.capsule_create(context, host_state['host'], new_capsule, - requested_networks, requested_volumes, - host_state['limits']) - - def capsule_delete(self, context, capsule): - return self.rpcapi.capsule_delete(context, capsule) - def network_detach(self, context, container, *args): self._record_action_start(context, container, container_actions.NETWORK_DETACH) diff --git a/zun/compute/manager.py b/zun/compute/manager.py index 4fe5ec3eb..b1816907b 100644 --- a/zun/compute/manager.py +++ b/zun/compute/manager.py @@ -319,9 +319,15 @@ class Manager(periodic_task.PeriodicTasks): self.driver.read_tar_image(image) if image['tag'] != tag: LOG.warning("The input tag is different from the tag in tar") - container = self.driver.create(context, container, image, - requested_networks, - requested_volumes) + if isinstance(container, objects.Capsule): + container = self.driver.create_capsule(context, container, + image, + requested_networks, + requested_volumes) + elif isinstance(container, objects.Container): + container = self.driver.create(context, container, image, + requested_networks, + requested_volumes) self._update_task_state(context, container, None) return container except exception.DockerError as e: @@ -502,7 +508,11 @@ class Manager(periodic_task.PeriodicTasks): self._update_task_state(context, container, consts.CONTAINER_DELETING) reraise = not force try: - self.driver.delete(context, container, force) + if isinstance(container, objects.Capsule): + self.driver.delete_capsule(context, container, force) + elif isinstance(container, objects.Container): + self.driver.delete(context, container, force) + if self.use_sandbox: self._delete_sandbox(context, container, reraise) except exception.DockerError as e: @@ -650,7 +660,10 @@ class Manager(periodic_task.PeriodicTasks): try: self._update_task_state(context, container, consts.CONTAINER_DELETING) - self.driver.delete(context, container, True) + if isinstance(container, objects.Capsule): + self.driver.delete_capsule(context, container) + elif isinstance(container, objects.Container): + self.driver.delete(context, container, True) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error("Rebuild container: %s failed, " @@ -1162,83 +1175,6 @@ class Manager(periodic_task.PeriodicTasks): self.driver.update_containers_states(ctx, containers, self) capsules = objects.Capsule.list(ctx) self.driver.update_containers_states(ctx, capsules, self) - LOG.debug('Complete syncing container states.') - - def capsule_create(self, context, capsule, requested_networks, - requested_volumes, limits): - @utils.synchronized("capsule-" + capsule.uuid) - def do_capsule_create(): - self._do_capsule_create(context, capsule, requested_networks, - requested_volumes, limits) - - utils.spawn_n(do_capsule_create) - - def _do_capsule_create(self, context, capsule, - requested_networks=None, - requested_volumes=None, - limits=None): - """Create capsule in the compute node - - :param context: security context - :param capsule: the special capsule object - :param requested_networks: the network ports that capsule will - connect - :param requested_volumes: the volume that capsule need - :param limits: no use field now. - """ - # NOTE(kevinz): Here create the sandbox container for the - # first function container --> capsule.containers[1]. - # capsule.containers[0] will only be used as recording the - # the sandbox_container info, and the sandbox_id of this contianer - # is itself. - sandbox_id = self._create_sandbox(context, - capsule, - requested_networks) - # Create init containers first - if capsule.init_containers: - for container in capsule.init_containers: - self._do_capsule_create_each_container(context, - capsule, - container, - sandbox_id, - limits, - requested_volumes, - requested_networks) - for container in capsule.init_containers: - self._wait_for_containers_completed(context, container) - - # Create common containers - for container in capsule.containers: - self._do_capsule_create_each_container(context, - capsule, - container, - sandbox_id, - limits, - requested_volumes, - requested_networks) - - capsule.host = self.host - capsule.status = consts.RUNNING - capsule.save(context) - - def capsule_delete(self, context, capsule): - # NOTE(kevinz): Delete functional containers first and then delete - # sandbox container - for container in (capsule.containers + capsule.init_containers): - try: - self._do_container_delete(context, container, force=True) - except Exception as e: - uuid = container.uuid - LOG.exception("Failed to delete container %(uuid0)s because " - "it doesn't exist in the capsule. Stale data " - "identified by %(uuid1)s is deleted from " - "database: %(error)s", - {'uuid0': uuid, 'uuid1': uuid, 'error': e}) - try: - self._delete_sandbox(context, capsule, reraise=False) - self._do_container_delete(context, capsule, force=True) - except Exception as e: - LOG.exception(e) def network_detach(self, context, container, network): @utils.synchronized(container.uuid) @@ -1292,47 +1228,3 @@ class Manager(periodic_task.PeriodicTasks): self.container_update(context, container, patch) utils.spawn_n(do_container_resize) - - def _do_capsule_create_each_container(self, context, capsule, - container, sandbox_id, - limits=None, - requested_volumes=None, - requested_networks=None): - container_requested_volumes = [] - container.set_sandbox_id(sandbox_id) - container.addresses = capsule.addresses - container_name = container.name - for volume in requested_volumes: - if volume.get(container_name, None): - container_requested_volumes.append( - volume.get(container_name)) - self._attach_volumes(context, container, - container_requested_volumes) - # Make sure the sandbox_id is set into meta. If not, - # when container delete, it will delete container network - # without considering sandbox. - container.save(context) - # Add volume assignment - created_container = \ - self._do_container_create_base(context, - container, - requested_networks, - container_requested_volumes, - sandbox=capsule, - limits=limits) - self._do_container_start(context, created_container) - - def _wait_for_containers_completed(self, context, container, - timeout=60, poll_interval=1): - start_time = time.time() - while time.time() - start_time < timeout: - container = self.driver.show(context, container) - if container.status == consts.STOPPED: - return - time.sleep(poll_interval) - - msg = _('Init container %(container_name)s failed: ') % { - 'container_name': container.name - } - self._fail_container(context, container, msg, unset_host=True) - raise exception.Invalid(msg) diff --git a/zun/compute/rpcapi.py b/zun/compute/rpcapi.py index 7104ac235..8c3f4563d 100644 --- a/zun/compute/rpcapi.py +++ b/zun/compute/rpcapi.py @@ -66,7 +66,7 @@ class API(rpc_service.API): pci_requests=pci_requests) @check_container_host - def container_delete(self, context, container, force): + def container_delete(self, context, container, force=False): return self._cast(container.host, 'container_delete', container=container, force=force) diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index ee8421da5..8e681e271 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -1260,3 +1260,120 @@ class DockerDriver(driver.ContainerDriver): network_api = zun_network.api(context, docker_api=docker) network_api.remove_network(network) + + def create_capsule(self, context, capsule, image, requested_networks, + requested_volumes): + capsule = self.create(context, capsule, image, requested_networks, + requested_volumes) + self.start(context, capsule) + for container in capsule.containers: + self._create_container_in_capsule(context, capsule, container, + requested_networks, + requested_volumes) + return capsule + + def _create_container_in_capsule(self, context, capsule, container, + requested_volumes, requested_networks): + # pull image + image_driver_name = container.image_driver + repo, tag = utils.parse_image_name(container.image, image_driver_name) + image_pull_policy = utils.get_image_pull_policy( + container.image_pull_policy, tag) + image, image_loaded = self.pull_image( + context, repo, tag, image_pull_policy, image_driver_name) + image['repo'], image['tag'] = repo, tag + if not image_loaded: + self.load_image(image['path']) + if image_driver_name == 'glance': + self.read_tar_image(image) + if image['tag'] != tag: + LOG.warning("The input tag is different from the tag in tar") + + # create container + with docker_utils.docker_client() as docker: + name = container.name + LOG.debug('Creating container with image %(image)s name %(name)s', + {'image': image['image'], 'name': name}) + binds = self._get_binds(context, requested_volumes) + kwargs = { + 'name': self.get_container_name(container), + 'command': container.command, + 'environment': container.environment, + 'working_dir': container.workdir, + 'labels': container.labels, + 'tty': container.interactive, + 'stdin_open': container.interactive, + } + + host_config = {} + host_config['privileged'] = container.privileged + host_config['binds'] = binds + kwargs['volumes'] = [b['bind'] for b in binds.values()] + host_config['network_mode'] = 'container:%s' % capsule.container_id + # TODO(hongbin): Uncomment this after docker-py add support for + # container mode for pid namespace. + # host_config['pid_mode'] = 'container:%s' % capsule.container_id + host_config['ipc_mode'] = 'container:%s' % capsule.container_id + if container.auto_remove: + host_config['auto_remove'] = container.auto_remove + if container.memory is not None: + host_config['mem_limit'] = str(container.memory) + 'M' + if container.cpu is not None: + host_config['cpu_quota'] = int(100000 * container.cpu) + host_config['cpu_period'] = 100000 + if container.restart_policy: + count = int(container.restart_policy['MaximumRetryCount']) + name = container.restart_policy['Name'] + host_config['restart_policy'] = {'Name': name, + 'MaximumRetryCount': count} + + if container.disk: + disk_size = str(container.disk) + 'G' + host_config['storage_opt'] = {'size': disk_size} + # The time unit in docker of heath checking is us, and the unit + # of interval and timeout is seconds. + if container.healthcheck: + healthcheck = {} + healthcheck['test'] = container.healthcheck.get('test', '') + interval = container.healthcheck.get('interval', 0) + healthcheck['interval'] = interval * 10 ** 9 + healthcheck['retries'] = int(container.healthcheck. + get('retries', 0)) + timeout = container.healthcheck.get('timeout', 0) + healthcheck['timeout'] = timeout * 10 ** 9 + kwargs['healthcheck'] = healthcheck + + kwargs['host_config'] = docker.create_host_config(**host_config) + if image['tag']: + image_repo = image['repo'] + ":" + image['tag'] + else: + image_repo = image['repo'] + response = docker.create_container(image_repo, **kwargs) + container.container_id = response['Id'] + docker.start(container.container_id) + + response = docker.inspect_container(container.container_id) + self._populate_container(container, response) + container.save(context) + + def delete_capsule(self, context, capsule, force): + for container in capsule.containers: + self._delete_container_in_capsule(context, capsule, container, + force) + self.delete(context, capsule, force) + + def _delete_container_in_capsule(self, context, capsule, container, force): + if not container.container_id: + return + + with docker_utils.docker_client() as docker: + try: + docker.stop(container.container_id) + docker.remove_container(container.container_id, + force=force) + except errors.APIError as api_error: + if is_not_found(api_error): + return + if is_not_connected(api_error): + return + raise diff --git a/zun/container/driver.py b/zun/container/driver.py index 6e77d6d36..a0d5ab825 100644 --- a/zun/container/driver.py +++ b/zun/container/driver.py @@ -297,3 +297,9 @@ class ContainerDriver(object): def delete_image(self, context, img_id, image_driver): raise NotImplementedError() + + def create_capsule(self, context, capsule, **kwargs): + raise NotImplementedError() + + def delete_capsule(self, context, capsule, **kwargs): + raise NotImplementedError() diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index 741d8a7a4..94099ba42 100644 --- a/zun/tests/unit/api/base.py +++ b/zun/tests/unit/api/base.py @@ -26,7 +26,7 @@ from zun.tests.unit.db import base PATH_PREFIX = '/v1' -CURRENT_VERSION = "container 1.31" +CURRENT_VERSION = "container 1.32" 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 bc3f91396..4c3e0267a 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.31', + 'max_version': '1.32', '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.31', + 'max_version': '1.32', 'min_version': '1.1', 'status': 'CURRENT'}]} diff --git a/zun/tests/unit/api/controllers/v1/test_capsules.py b/zun/tests/unit/api/controllers/v1/test_capsules.py index a05be45d3..1e2362a03 100644 --- a/zun/tests/unit/api/controllers/v1/test_capsules.py +++ b/zun/tests/unit/api/controllers/v1/test_capsules.py @@ -21,7 +21,7 @@ from zun.tests.unit.db import utils class TestCapsuleController(api_base.FunctionalTest): - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule(self, mock_capsule_create, mock_neutron_get_network): @@ -56,7 +56,7 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertTrue(mock_capsule_create.called) self.assertTrue(mock_neutron_get_network.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_two_containers(self, mock_capsule_create, mock_neutron_get_network): @@ -92,7 +92,7 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertTrue(mock_capsule_create.called) self.assertTrue(mock_neutron_get_network.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.common.utils.check_capsule_template') def test_create_capsule_wrong_kind_set(self, mock_check_template, mock_capsule_create): @@ -109,7 +109,7 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertFalse(mock_capsule_create.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.common.utils.check_capsule_template') def test_create_capsule_less_than_one_container(self, mock_check_template, mock_capsule_create): @@ -123,7 +123,7 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertFalse(mock_capsule_create.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.common.utils.check_capsule_template') def test_create_capsule_no_container_field(self, mock_check_template, mock_capsule_create): @@ -137,7 +137,7 @@ class TestCapsuleController(api_base.FunctionalTest): params=params, content_type='application/json') self.assertFalse(mock_capsule_create.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.common.utils.check_capsule_template') def test_create_capsule_no_container_image(self, mock_check_template, mock_capsule_create): @@ -152,7 +152,7 @@ class TestCapsuleController(api_base.FunctionalTest): params=params, content_type='application/json') self.assertFalse(mock_capsule_create.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_with_init_containers(self, mock_capsule_create, mock_neutron_get_network): @@ -192,7 +192,7 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertTrue(mock_capsule_create.called) self.assertTrue(mock_neutron_get_network.called) - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_with_two_init_containers(self, mock_capsule_create, mock_neutron_get_network): @@ -233,7 +233,7 @@ class TestCapsuleController(api_base.FunctionalTest): @patch('zun.volume.cinder_api.CinderAPI.ensure_volume_usable') @patch('zun.volume.cinder_api.CinderAPI.create_volume') - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_with_create_new_volume(self, mock_capsule_create, mock_neutron_get_network, @@ -283,7 +283,7 @@ class TestCapsuleController(api_base.FunctionalTest): @patch('zun.volume.cinder_api.CinderAPI.ensure_volume_usable') @patch('zun.volume.cinder_api.CinderAPI.search_volume') - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_with_existed_volume(self, mock_capsule_create, mock_neutron_get_network, @@ -336,7 +336,7 @@ class TestCapsuleController(api_base.FunctionalTest): @patch('zun.volume.cinder_api.CinderAPI.create_volume') @patch('zun.volume.cinder_api.CinderAPI.ensure_volume_usable') @patch('zun.volume.cinder_api.CinderAPI.search_volume') - @patch('zun.compute.api.API.capsule_create') + @patch('zun.compute.api.API.container_create') @patch('zun.network.neutron.NeutronAPI.get_available_network') def test_create_capsule_with_two_volumes(self, mock_capsule_create, mock_neutron_get_network, @@ -441,18 +441,14 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertEqual(test_capsule['uuid'], response.json['uuid']) - @patch('zun.compute.api.API.capsule_delete') + @patch('zun.compute.api.API.container_delete') + @patch('zun.compute.api.API.container_stop') @patch('zun.objects.Capsule.get_by_uuid') - @patch('zun.objects.Container.get_by_uuid') @patch('zun.objects.Capsule.save') def test_delete_capsule_by_uuid(self, mock_capsule_save, - mock_container_get_by_uuid, mock_capsule_get_by_uuid, + mock_capsule_stop, mock_capsule_delete): - test_container = utils.get_test_container() - test_container_obj = objects.Container(self.context, **test_container) - mock_container_get_by_uuid.return_value = test_container_obj - test_capsule = utils.create_test_container(context=self.context) test_capsule_obj = objects.Capsule(self.context, **test_capsule) @@ -464,26 +460,23 @@ class TestCapsuleController(api_base.FunctionalTest): response = self.app.delete('/v1/capsules/%s' % capsule_uuid) self.assertTrue(mock_capsule_delete.called) + self.assertTrue(mock_capsule_stop.called) self.assertEqual(204, response.status_int) context = mock_capsule_save.call_args[0][0] self.assertIs(False, context.all_projects) @patch('zun.common.policy.enforce') - @patch('zun.compute.api.API.capsule_delete') + @patch('zun.compute.api.API.container_delete') + @patch('zun.compute.api.API.container_stop') @patch('zun.objects.Capsule.get_by_uuid') - @patch('zun.objects.Container.get_by_uuid') @patch('zun.objects.Capsule.save') def test_delete_capsule_by_uuid_all_projects(self, mock_capsule_save, - mock_container_get_by_uuid, mock_capsule_get_by_uuid, + mock_capsule_stop, mock_capsule_delete, mock_policy): mock_policy.return_value = True - test_container = utils.get_test_container() - test_container_obj = objects.Container(self.context, **test_container) - mock_container_get_by_uuid.return_value = test_container_obj - test_capsule = utils.create_test_container(context=self.context) test_capsule_obj = objects.Capsule(self.context, **test_capsule) @@ -496,6 +489,7 @@ class TestCapsuleController(api_base.FunctionalTest): '/v1/capsules/%s/?all_projects=1' % capsule_uuid) self.assertTrue(mock_capsule_delete.called) + self.assertTrue(mock_capsule_stop.called) self.assertEqual(204, response.status_int) context = mock_capsule_save.call_args[0][0] self.assertIs(True, context.all_projects) @@ -505,18 +499,14 @@ class TestCapsuleController(api_base.FunctionalTest): self.assertRaises(AppError, self.app.delete, '/capsules/%s' % uuid) - @patch('zun.compute.api.API.capsule_delete') + @patch('zun.compute.api.API.container_delete') + @patch('zun.compute.api.API.container_stop') @patch('zun.objects.Capsule.get_by_name') - @patch('zun.objects.Container.get_by_uuid') @patch('zun.objects.Capsule.save') def test_delete_capsule_by_name(self, mock_capsule_save, - mock_container_get_by_name, mock_capsule_get_by_uuid, + mock_capsule_stop, mock_capsule_delete): - test_container = utils.get_test_container() - test_container_obj = objects.Container(self.context, **test_container) - mock_container_get_by_name.return_value = test_container_obj - test_capsule = utils.create_test_container(context=self.context) test_capsule_obj = objects.Capsule(self.context, **test_capsule) @@ -529,6 +519,7 @@ class TestCapsuleController(api_base.FunctionalTest): capsule_name) self.assertTrue(mock_capsule_delete.called) + self.assertTrue(mock_capsule_stop.called) self.assertEqual(204, response.status_int) context = mock_capsule_save.call_args[0][0] self.assertIs(False, context.all_projects) diff --git a/zun/tests/unit/compute/test_compute_api.py b/zun/tests/unit/compute/test_compute_api.py index a5ddfbd1c..52d0ac8d5 100644 --- a/zun/tests/unit/compute/test_compute_api.py +++ b/zun/tests/unit/compute/test_compute_api.py @@ -116,14 +116,6 @@ class TestAPI(base.TestCase): self.assertTrue(mock_save.called) self.assertEqual(consts.ERROR, container.status) - @mock.patch('zun.compute.rpcapi.API._call') - def test_capsule_delete(self, mock_call): - capsule = self.container - self.compute_api.capsule_delete( - self.context, capsule) - mock_call.assert_called_once_with( - capsule.host, "capsule_delete", capsule=capsule) - @mock.patch('zun.compute.rpcapi.API._cast') @mock.patch('zun.api.servicegroup.ServiceGroup.service_is_up') @mock.patch('zun.objects.ZunService.list_by_binary')