diff --git a/.zuul.yaml b/.zuul.yaml index 5e9a18b20..686b1ed5b 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')