Merge "Make start/stop/pause/unpause/restart/kill action async"

This commit is contained in:
Jenkins 2017-01-09 16:06:42 +00:00 committed by Gerrit Code Review
commit 62482a6acc
9 changed files with 365 additions and 318 deletions

View File

@ -14,6 +14,7 @@
# under the License.
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import timeutils
import pecan
from pecan import rest
@ -28,6 +29,7 @@ from zun.common import exception
from zun.common.i18n import _LE
from zun.common import name_generator
from zun.common import policy
from zun.common import utils
from zun.common import validation
from zun import objects
from zun.objects import fields
@ -330,6 +332,9 @@ class ContainersController(rest.RestController):
"""
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:delete")
force = strutils.bool_from_string(force, strict=True)
if not force:
utils.validate_container_state(container, 'delete')
context = pecan.request.context
pecan.request.rpcapi.container_delete(context, container, force)
container.destroy()
@ -340,57 +345,60 @@ class ContainersController(rest.RestController):
def start(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:start")
utils.validate_container_state(container, 'start')
LOG.debug('Calling compute.container_start with %s',
container.uuid)
context = pecan.request.context
container = pecan.request.rpcapi.container_start(context, container)
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_start(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def stop(self, container_id, timeout=None, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:stop")
utils.validate_container_state(container, 'stop')
LOG.debug('Calling compute.container_stop with %s' %
container.uuid)
context = pecan.request.context
container = pecan.request.rpcapi.container_stop(context, container,
timeout)
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_stop(context, container, timeout)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def reboot(self, container_id, timeout=None, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:reboot")
utils.validate_container_state(container, 'reboot')
LOG.debug('Calling compute.container_reboot with %s' %
container.uuid)
context = pecan.request.context
container = pecan.request.rpcapi.container_reboot(context, container,
timeout)
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_reboot(context, container, timeout)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def pause(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:pause")
utils.validate_container_state(container, 'pause')
LOG.debug('Calling compute.container_pause with %s' %
container.uuid)
context = pecan.request.context
container = pecan.request.rpcapi.container_pause(context, container)
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_pause(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def unpause(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:unpause")
utils.validate_container_state(container, 'unpause')
LOG.debug('Calling compute.container_unpause with %s' %
container.uuid)
context = pecan.request.context
container = pecan.request.rpcapi.container_unpause(context, container)
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_unpause(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@ -407,6 +415,7 @@ class ContainersController(rest.RestController):
def execute(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:execute")
utils.validate_container_state(container, 'execute')
LOG.debug('Calling compute.container_exec with %s command %s'
% (container.uuid, kw['command']))
context = pecan.request.context
@ -418,9 +427,10 @@ class ContainersController(rest.RestController):
def kill(self, container_id, **kw):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(), "container:kill")
utils.validate_container_state(container, 'kill')
LOG.debug('Calling compute.container_kill with %s signal %s'
% (container.uuid, kw.get('signal', kw.get('signal'))))
context = pecan.request.context
container = pecan.request.rpcapi.container_kill(context, container,
kw.get('signal'))
return Container.convert_with_links(container.as_dict())
pecan.request.rpcapi.container_kill(context, container,
kw.get('signal', None))
pecan.response.status = 202

View File

@ -35,6 +35,26 @@ from zun.common.i18n import _LW
LOG = logging.getLogger(__name__)
VALID_STATES = {
'delete': ['Stopped', 'Error'],
'start': ['Stopped'],
'stop': ['Running'],
'reboot': ['Running', 'Stopped'],
'pause': ['Running'],
'unpause': ['Paused'],
'kill': ['Running'],
'execute': ['Running'],
}
def validate_container_state(container, action):
if container.status not in VALID_STATES[action]:
raise exception.InvalidStateException(
id=container.uuid,
action=action,
actual_state=container.status)
def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty

View File

@ -16,7 +16,6 @@ import six
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from zun.common import exception
from zun.common.i18n import _LE
@ -29,17 +28,6 @@ from zun.objects import fields
LOG = logging.getLogger(__name__)
VALID_STATES = {
'delete': ['Stopped', 'Error'],
'start': ['Stopped'],
'stop': ['Running'],
'reboot': ['Running', 'Stopped'],
'pause': ['Running'],
'unpause': ['Paused'],
'kill': ['Running'],
'exec': ['Running'],
}
class Manager(object):
'''Manages the running containers.'''
@ -54,17 +42,9 @@ class Manager(object):
container.task_state = None
container.save()
def _validate_container_state(self, container, action):
if container.status not in VALID_STATES[action]:
raise exception.InvalidStateException(
id=container.uuid,
action=action,
actual_state=container.status)
def container_create(self, context, container):
utils.spawn_n(self._do_container_create, context, container)
@translate_exception
def container_run(self, context, container):
utils.spawn_n(self._do_container_run, context, container)
@ -156,32 +136,28 @@ class Manager(object):
self._fail_container(container, six.text_type(e))
return
def _do_container_start(self, context, container):
def _do_container_start(self, context, container, reraise=False):
LOG.debug('Starting container: %s', container.uuid)
try:
# Although we dont need this validation, but i still
# keep it for extra surity
self._validate_container_state(container, 'start')
container = self.driver.start(container)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker start API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker start API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
self._fail_container(container, six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
@translate_exception
def container_delete(self, context, container, force):
LOG.debug('Deleting container: %s', container.uuid)
try:
force = strutils.bool_from_string(force, strict=True)
if not force:
self._validate_container_state(container, 'delete')
self.driver.delete(container, force)
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker "
@ -230,74 +206,83 @@ class Manager(object):
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
raise
@translate_exception
def container_reboot(self, context, container, timeout):
def _do_container_reboot(self, context, container, timeout, reraise=False):
LOG.debug('Rebooting container: %s', container.uuid)
try:
self._validate_container_state(container, 'reboot')
container = self.driver.reboot(container, timeout)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker reboot "
"API: %s"), six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE("Error occurred while calling Docker reboot "
"API: %s"), six.text_type(e))
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
@translate_exception
def container_stop(self, context, container, timeout):
def container_reboot(self, context, container, timeout):
utils.spawn_n(self._do_container_reboot, context, container, timeout)
def _do_container_stop(self, context, container, timeout, reraise=False):
LOG.debug('Stopping container: %s', container.uuid)
try:
self._validate_container_state(container, 'stop')
container = self.driver.stop(container, timeout)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker stop API: %s"),
six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker stop API: %s"),
six.text_type(e))
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
def container_stop(self, context, container, timeout):
utils.spawn_n(self._do_container_stop, context, container, timeout)
@translate_exception
def container_start(self, context, container):
return self._do_container_start(context, container)
utils.spawn_n(self._do_container_start, context, container)
@translate_exception
def container_pause(self, context, container):
def _do_container_pause(self, context, container, reraise=False):
LOG.debug('Pausing container: %s', container.uuid)
try:
self._validate_container_state(container, 'pause')
container = self.driver.pause(container)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker pause API: %s"),
six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker pause API: %s"),
six.text_type(e))
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s,"), six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s,"),
six.text_type(e))
@translate_exception
def container_unpause(self, context, container):
def container_pause(self, context, container):
utils.spawn_n(self._do_container_pause, context, container)
def _do_container_unpause(self, context, container, reraise=False):
LOG.debug('Unpausing container: %s', container.uuid)
try:
self._validate_container_state(container, 'unpause')
container = self.driver.unpause(container)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker unpause "
"API: %s"),
six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker unpause API: %s"),
six.text_type(e))
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
def container_unpause(self, context, container):
utils.spawn_n(self._do_container_unpause, context, container)
@translate_exception
def container_logs(self, context, container):
@ -317,7 +302,6 @@ class Manager(object):
# TODO(hongbin): support exec command interactively
LOG.debug('Executing command in container: %s', container.uuid)
try:
self._validate_container_state(container, 'exec')
return self.driver.execute(container, command)
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker exec API: %s"),
@ -327,18 +311,20 @@ class Manager(object):
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
raise
@translate_exception
def container_kill(self, context, container, signal):
def _do_container_kill(self, context, container, signal, reraise=False):
LOG.debug('kill signal to container: %s', container.uuid)
try:
self._validate_container_state(container, 'kill')
container = self.driver.kill(container, signal)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker kill API: %s"),
six.text_type(e))
raise
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker kill API: %s"),
six.text_type(e))
def container_kill(self, context, container, signal):
utils.spawn_n(self._do_container_kill, context, container, signal)
def image_pull(self, context, image):
utils.spawn_n(self._do_image_pull, context, image)

View File

@ -47,21 +47,21 @@ class API(rpc_service.API):
return self._call('container_show', container=container)
def container_reboot(self, context, container, timeout):
return self._call('container_reboot', container=container,
return self._cast('container_reboot', container=container,
timeout=timeout)
def container_stop(self, context, container, timeout):
return self._call('container_stop', container=container,
return self._cast('container_stop', container=container,
timeout=timeout)
def container_start(self, context, container):
return self._call('container_start', container=container)
return self._cast('container_start', container=container)
def container_pause(self, context, container):
return self._call('container_pause', container=container)
return self._cast('container_pause', container=container)
def container_unpause(self, context, container):
return self._call('container_unpause', container=container)
return self._cast('container_unpause', container=container)
def container_logs(self, context, container):
return self._call('container_logs', container=container)
@ -71,7 +71,7 @@ class API(rpc_service.API):
command=command)
def container_kill(self, context, container, signal):
return self._call('container_kill', container=container,
return self._cast('container_kill', container=container,
signal=signal)
def image_show(self, context, image):

View File

@ -123,34 +123,28 @@ class ZunClient(rest_client.RestClient):
self.container_uri(container_id, params=params), **kwargs)
def start_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='start'), None, **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def stop_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='stop'), None, *kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def pause_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='pause'), None, **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def unpause_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='unpause'), None, **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def kill_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='kill'), None, **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def reboot_container(self, container_id, **kwargs):
resp, body = self.post(
return self.post(
self.container_uri(container_id, action='reboot'), None, **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def exec_container(self, container_id, command, **kwargs):
return self.post(
@ -166,25 +160,14 @@ class ZunClient(rest_client.RestClient):
return self.deserialize(resp, body,
service_model.ServiceCollection)
def ensure_container_created(self, container_id):
def container_created():
def ensure_container_in_desired_state(self, container_id, status):
def is_container_in_desired_state():
_, container = self.get_container(container_id)
if container.status == 'Creating':
return False
else:
return True
utils.wait_for_condition(container_created)
def ensure_container_started(self, container_id):
def container_started():
_, container = self.get_container(container_id)
if container.status == 'Running':
if container.status == status:
return True
else:
return False
utils.wait_for_condition(container_started)
utils.wait_for_condition(is_container_in_desired_state)
class DockerClient(object):
@ -195,3 +178,13 @@ class DockerClient(object):
if container_id in info['Name']:
return info
return None
def ensure_container_pid_changed(self, container_id, pid):
def is_pid_changed():
container = self.get_container(container_id)
new_pid = container.get('State').get('Pid')
if pid != new_pid:
return True
else:
return False
utils.wait_for_condition(is_pid_changed)

View File

@ -81,37 +81,42 @@ class TestContainer(base.BaseZunTest):
def test_start_stop_container(self):
_, model = self._run_container()
resp, model = self.container_client.stop_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Stopped', model.status)
resp, _ = self.container_client.stop_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Stopped')
self.assertEqual('Stopped', self._get_container_state(model.uuid))
resp, model = self.container_client.start_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Running', model.status)
resp, _ = self.container_client.start_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
self.assertEqual('Running', self._get_container_state(model.uuid))
@decorators.idempotent_id('b5f39756-8898-4e0e-a48b-dda0a06b66b6')
def test_pause_unpause_container(self):
_, model = self._run_container()
resp, model = self.container_client.pause_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Paused', model.status)
resp, _ = self.container_client.pause_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Paused')
self.assertEqual('Paused', self._get_container_state(model.uuid))
resp, model = self.container_client.unpause_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Running', model.status)
resp, _ = self.container_client.unpause_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
self.assertEqual('Running', self._get_container_state(model.uuid))
@decorators.idempotent_id('6179a588-3d48-4372-9599-f228411d1449')
def test_kill_container(self):
_, model = self._run_container()
resp, model = self.container_client.kill_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Stopped', model.status)
resp, _ = self.container_client.kill_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Stopped')
self.assertEqual('Stopped', self._get_container_state(model.uuid))
@decorators.idempotent_id('c2e54321-0a70-4331-ba62-9dcaa75ac250')
@ -120,9 +125,9 @@ class TestContainer(base.BaseZunTest):
container = self.docker_client.get_container(model.uuid)
pid = container.get('State').get('Pid')
resp, model = self.container_client.reboot_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Running', model.status)
resp, _ = self.container_client.reboot_container(model.uuid)
self.assertEqual(202, resp.status)
self.docker_client.ensure_container_pid_changed(model.uuid, pid)
self.assertEqual('Running', self._get_container_state(model.uuid))
# assert pid is changed
container = self.docker_client.get_container(model.uuid)
@ -149,7 +154,8 @@ class TestContainer(base.BaseZunTest):
resp, model = self.container_client.post_container(gen_model)
self.assertEqual(202, resp.status)
# Wait for container to finish creation
self.container_client.ensure_container_created(model.uuid)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Stopped')
# Assert the container is created
resp, model = self.container_client.get_container(model.uuid)
@ -163,13 +169,13 @@ class TestContainer(base.BaseZunTest):
resp, model = self.container_client.run_container(gen_model)
self.assertEqual(202, resp.status)
# Wait for container to started
self.container_client.ensure_container_started(model.uuid)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
# Assert the container is started
resp, model = self.container_client.get_container(model.uuid)
self.assertTrue(model.status in ['Running', 'Stopped'])
self.assertTrue(self._get_container_state(model.uuid) in
['Running', 'Stopped'])
self.assertEqual('Running', model.status)
self.assertEqual('Running', self._get_container_state(model.uuid))
return resp, model
def _delete_container(self, container_id):

View File

@ -146,7 +146,8 @@ class TestContainerController(api_base.FunctionalTest):
self.assertEqual({"key1": "val1", "key2": "val2"},
c.get('environment'))
# Delete the container we created
response = self.app.delete('/v1/containers/%s/' % c.get('uuid'))
response = self.app.delete(
'/v1/containers/%s?force=True' % c.get('uuid'))
self.assertEqual(204, response.status_int)
response = self.app.get('/v1/containers/')
@ -415,7 +416,7 @@ class TestContainerController(api_base.FunctionalTest):
self.assertEqual('new_name', test_container_obj.name)
def _action_test(self, container, action, ident_field,
mock_container_action, query_param=''):
mock_container_action, status_code, query_param=''):
test_container_obj = objects.Container(self.context, **container)
ident = container.get(ident_field)
get_by_ident_loc = 'zun.objects.Container.get_by_%s' % ident_field
@ -423,7 +424,7 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_indent.return_value = test_container_obj
response = self.app.post('/v1/containers/%s/%s/?%s' %
(ident, action, query_param))
self.assertEqual(200, response.status_int)
self.assertEqual(status_code, response.status_int)
# Only PUT should work, others like GET should fail
self.assertRaises(AppError, self.app.get,
@ -437,98 +438,154 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_action.assert_called_once_with(
mock.ANY, test_container_obj)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_start')
def test_start_by_uuid(self, mock_container_start):
def test_start_by_uuid(self, mock_container_start, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_start.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'start', 'uuid',
mock_container_start)
mock_container_start, 202)
def test_start_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Running')
with self.assertRaisesRegexp(
AppError, "Cannot start container %s in Running state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'start'))
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_start')
def test_start_by_name(self, mock_container_start):
def test_start_by_name(self, mock_container_start, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_start.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'start', 'name',
mock_container_start)
mock_container_start, 202)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_stop')
def test_stop_by_uuid(self, mock_container_stop):
def test_stop_by_uuid(self, mock_container_stop, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_stop.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'stop', 'uuid',
mock_container_stop,
mock_container_stop, 202,
query_param='timeout=10')
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_stop')
def test_stop_by_name(self, mock_container_stop):
def test_stop_by_name(self, mock_container_stop, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_stop.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'stop', 'name',
mock_container_stop,
mock_container_stop, 202,
query_param='timeout=10')
def test_stop_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Stopped')
with self.assertRaisesRegexp(
AppError, "Cannot stop container %s in Stopped state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'stop'))
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_pause')
def test_pause_by_uuid(self, mock_container_pause):
def test_pause_by_uuid(self, mock_container_pause, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_pause.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'pause', 'uuid',
mock_container_pause)
mock_container_pause, 202)
def test_pause_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Stopped')
with self.assertRaisesRegexp(
AppError, "Cannot pause container %s in Stopped state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'pause'))
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_pause')
def test_pause_by_name(self, mock_container_pause):
def test_pause_by_name(self, mock_container_pause, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_pause.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'pause', 'name',
mock_container_pause)
mock_container_pause, 202)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_unpause')
def test_unpause_by_uuid(self, mock_container_unpause):
def test_unpause_by_uuid(self, mock_container_unpause, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_unpause.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'unpause', 'uuid',
mock_container_unpause)
mock_container_unpause, 202)
def test_unpause_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Running')
with self.assertRaisesRegexp(
AppError,
"Cannot unpause container %s in Running state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'unpause'))
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_unpause')
def test_unpause_by_name(self, mock_container_unpause):
def test_unpause_by_name(self, mock_container_unpause, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_unpause.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'unpause', 'name',
mock_container_unpause)
mock_container_unpause, 202)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_reboot')
def test_reboot_by_uuid(self, mock_container_reboot):
def test_reboot_by_uuid(self, mock_container_reboot, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_reboot.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'reboot', 'uuid',
mock_container_reboot,
mock_container_reboot, 202,
query_param='timeout=10')
def test_reboot_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Paused')
with self.assertRaisesRegexp(
AppError, "Cannot reboot container %s in Paused state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'reboot'))
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_reboot')
def test_reboot_by_name(self, mock_container_reboot):
def test_reboot_by_name(self, mock_container_reboot, mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_reboot.return_value = test_container_obj
test_container = utils.get_test_container()
self._action_test(test_container, 'reboot', 'name',
mock_container_reboot,
mock_container_reboot, 202,
query_param='timeout=10')
@patch('zun.compute.rpcapi.API.container_logs')
@ -573,10 +630,11 @@ class TestContainerController(api_base.FunctionalTest):
'/v1/containers/%s/logs/' % container_uuid)
self.assertFalse(mock_container_logs.called)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_exec')
@patch('zun.objects.Container.get_by_uuid')
def test_execute_command_by_uuid(self, mock_get_by_uuid,
mock_container_exec):
mock_container_exec, mock_validate):
mock_container_exec.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
@ -590,10 +648,22 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_exec.assert_called_once_with(
mock.ANY, test_container_obj, cmd['command'])
def test_exec_command_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Stopped')
cmd = {'command': 'ls'}
with self.assertRaisesRegexp(
AppError,
"Cannot execute container %s in Stopped state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'execute'), cmd)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_exec')
@patch('zun.objects.Container.get_by_name')
def test_execute_command_by_name(self, mock_get_by_name,
mock_container_exec):
mock_container_exec, mock_validate):
mock_container_exec.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
@ -607,10 +677,11 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_exec.assert_called_once_with(
mock.ANY, test_container_obj, cmd['command'])
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_delete')
@patch('zun.objects.Container.get_by_uuid')
def test_delete_container_by_uuid(self, mock_get_by_uuid,
mock_container_delete):
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
@ -624,10 +695,29 @@ class TestContainerController(api_base.FunctionalTest):
mock.ANY, test_container_obj, False)
mock_destroy.assert_called_once_with()
def test_delete_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Running')
with self.assertRaisesRegexp(
AppError,
"Cannot delete container %s in Running state" % uuid):
self.app.delete('/v1/containers/%s' % (test_object.uuid))
@patch('zun.compute.rpcapi.API.container_delete')
def test_delete_by_uuid_invalid_state_force_true(self, mock_delete):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Running')
response = self.app.delete('/v1/containers/%s?force=True' % (
test_object.uuid))
self.assertEqual(204, response.status_int)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_delete')
@patch('zun.objects.Container.get_by_name')
def test_delete_container_by_name(self, mock_get_by_name,
mock_container_delete):
mock_container_delete, mock_validate):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_name.return_value = test_container_obj
@ -641,10 +731,12 @@ class TestContainerController(api_base.FunctionalTest):
mock.ANY, test_container_obj, False)
mock_destroy.assert_called_once_with()
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_kill')
@patch('zun.objects.Container.get_by_uuid')
def test_kill_container_by_uuid(self,
mock_get_by_uuid, mock_container_kill):
mock_get_by_uuid, mock_container_kill,
mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_kill.return_value = test_container_obj
@ -656,14 +748,26 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s/' % (container_uuid, 'kill')
cmd = {'signal': '9'}
response = self.app.post(url, cmd)
self.assertEqual(200, response.status_int)
self.assertEqual(202, response.status_int)
mock_container_kill.assert_called_once_with(
mock.ANY, test_container_obj, cmd['signal'])
def test_kill_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Stopped')
body = {'signal': 9}
with self.assertRaisesRegexp(
AppError, "Cannot kill container %s in Stopped state" % uuid):
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'kill'), body)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_kill')
@patch('zun.objects.Container.get_by_name')
def test_kill_container_by_name(self,
mock_get_by_name, mock_container_kill):
mock_get_by_name, mock_container_kill,
mock_validate):
test_container_obj = objects.Container(self.context,
**utils.get_test_container())
mock_container_kill.return_value = test_container_obj
@ -675,15 +779,17 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s/' % (container_name, 'kill')
cmd = {'signal': '9'}
response = self.app.post(url, cmd)
self.assertEqual(200, response.status_int)
self.assertEqual(202, response.status_int)
mock_container_kill.assert_called_once_with(
mock.ANY, test_container_obj, cmd['signal'])
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_kill')
@patch('zun.objects.Container.get_by_uuid')
def test_kill_container_which_not_exist(self,
mock_get_by_uuid,
mock_container_kill):
mock_container_kill,
mock_validate):
mock_container_kill.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
@ -695,11 +801,13 @@ class TestContainerController(api_base.FunctionalTest):
'/v1/containers/%s/%s/' % (container_uuid, 'kill'))
self.assertTrue(mock_container_kill.called)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_kill')
@patch('zun.objects.Container.get_by_uuid')
def test_kill_container_with_exception(self,
mock_get_by_uuid,
mock_container_kill):
mock_container_kill,
mock_validate):
mock_container_kill.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)

View File

@ -18,10 +18,12 @@ from zun.common import exception
from zun.common import utils
from zun.common.utils import check_container_id
from zun.common.utils import translate_exception
from zun.objects.container import Container
from zun.tests import base
from zun.tests.unit.db import utils as db_utils
class TestUtils(base.BaseTestCase):
class TestUtils(base.TestCase):
"""Test cases for zun.common.utils"""
def test_check_container_id(self):
@ -73,3 +75,31 @@ class TestUtils(base.BaseTestCase):
self.assertTrue(utils.should_pull_image('always', False))
self.assertTrue(utils.should_pull_image('ifnotpresent', False))
self.assertFalse(utils.should_pull_image('ifnotpresent', True))
def test_validate_container_state(self):
container = Container(self.context, **db_utils.get_test_container())
container.status = 'Stopped'
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
utils.validate_container_state(container, 'stop')
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
utils.validate_container_state(container, 'pause')
container.status = 'Running'
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
utils.validate_container_state(container, 'start')
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
utils.validate_container_state(container, 'unpause')
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
utils.validate_container_state(container, 'delete')
self.assertIsNone(utils.validate_container_state(
container, 'reboot'))
container.status = 'Stopped'
self.assertIsNone(utils.validate_container_state(
container, 'reboot'))
container.status = 'Running'
self.assertIsNone(utils.validate_container_state(
container, 'execute'))

View File

@ -42,33 +42,6 @@ class TestManager(base.TestCase):
self.assertEqual("Creation Failed", container.status_reason)
self.assertIsNone(container.task_state)
def test_validate_container_state(self):
container = Container(self.context, **utils.get_test_container())
container.status = 'Stopped'
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
self.compute_manager._validate_container_state(container, 'stop')
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
self.compute_manager._validate_container_state(container, 'pause')
container.status = 'Running'
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
self.compute_manager._validate_container_state(container, 'start')
with self.assertRaisesRegexp(exception.InvalidStateException,
"%s" % container.uuid):
self.compute_manager._validate_container_state(container,
'unpause')
container.status = 'Running'
self.assertIsNone(self.compute_manager._validate_container_state(
container, 'reboot'))
container.status = 'Stopped'
self.assertIsNone(self.compute_manager._validate_container_state(
container, 'reboot'))
container.status = 'Running'
self.assertIsNone(self.compute_manager._validate_container_state(
container, 'exec'))
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(fake_driver, 'create')
@ -218,24 +191,14 @@ class TestManager(base.TestCase):
mock_create.assert_called_once_with(container, None,
{'name': 'nginx', 'path': None})
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'delete')
def test_container_delete(self, mock_delete, mock_validate):
def test_container_delete(self, mock_delete):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_delete(self. context, container, False)
mock_delete.assert_called_once_with(container, False)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_delete_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_delete,
self.context, container, False)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'delete')
def test_container_delete_failed(self, mock_delete, mock_validate):
def test_container_delete_failed(self, mock_delete):
container = Container(self.context, **utils.get_test_container())
mock_delete.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
@ -268,130 +231,78 @@ class TestManager(base.TestCase):
self.compute_manager.container_show,
self.context, container)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'reboot')
def test_container_reboot(self, mock_reboot, mock_validate):
def test_container_reboot(self, mock_reboot):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_reboot(self.context, container, 10)
self.compute_manager._do_container_reboot(self.context, container, 10)
mock_reboot.assert_called_once_with(container, 10)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_reboot_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_reboot,
self.context, container, 10)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'reboot')
def test_container_reboot_failed(self, mock_reboot, mock_validate):
def test_container_reboot_failed(self, mock_reboot):
container = Container(self.context, **utils.get_test_container())
mock_reboot.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_reboot,
self.context, container, 10)
self.compute_manager._do_container_reboot,
self.context, container, 10, reraise=True)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'stop')
def test_container_stop(self, mock_stop, mock_validate):
def test_container_stop(self, mock_stop):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_stop(self.context, container, 10)
self.compute_manager._do_container_stop(self.context, container, 10)
mock_stop.assert_called_once_with(container, 10)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_stop_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_stop,
self.context, container, 10)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'stop')
def test_container_stop_failed(self, mock_stop, mock_validate):
def test_container_stop_failed(self, mock_stop):
container = Container(self.context, **utils.get_test_container())
mock_stop.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_stop,
self.context, container, 10)
self.compute_manager._do_container_stop,
self.context, container, 10, reraise=True)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'start')
def test_container_start(self, mock_start, mock_validate):
def test_container_start(self, mock_start):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_start(self.context, container)
self.compute_manager._do_container_start(self.context, container)
mock_start.assert_called_once_with(container)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(manager.Manager, '_fail_container')
def test_container_start_invalid_state(self, mock_fail, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_start,
self.context, container)
mock_fail.assert_called_once()
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(manager.Manager, '_fail_container')
@mock.patch.object(fake_driver, 'start')
def test_container_start_failed(self, mock_start,
mock_fail, mock_validate):
mock_fail):
container = Container(self.context, **utils.get_test_container())
mock_start.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_start,
self.context, container)
self.compute_manager._do_container_start,
self.context, container, reraise=True)
mock_fail.assert_called_once()
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'pause')
def test_container_pause(self, mock_pause, mock_validate):
def test_container_pause(self, mock_pause):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_pause(self.context, container)
self.compute_manager._do_container_pause(self.context, container)
mock_pause.assert_called_once_with(container)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_pause_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_pause,
self.context, container)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'pause')
def test_container_pause_failed(self, mock_pause, mock_validate):
def test_container_pause_failed(self, mock_pause):
container = Container(self.context, **utils.get_test_container())
mock_pause.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_pause,
self.context, container)
self.compute_manager._do_container_pause,
self.context, container, reraise=True)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'unpause')
def test_container_unpause(self, mock_unpause, mock_validate):
def test_container_unpause(self, mock_unpause):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_unpause(self.context, container)
self.compute_manager._do_container_unpause(self.context, container)
mock_unpause.assert_called_once_with(container)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_unpause_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_unpause,
self.context, container)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'unpause')
def test_container_unpause_failed(self, mock_unpause, mock_validate):
def test_container_unpause_failed(self, mock_unpause):
container = Container(self.context, **utils.get_test_container())
mock_unpause.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_unpause,
self.context, container)
self.compute_manager._do_container_unpause,
self.context, container, reraise=True)
@mock.patch.object(fake_driver, 'show_logs')
def test_container_logs(self, mock_logs):
@ -414,14 +325,6 @@ class TestManager(base.TestCase):
self.context, container, 'fake_cmd')
mock_execute.assert_called_once_with(container, 'fake_cmd')
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_execute_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_exec,
self.context, container, 'fake_cmd')
@mock.patch.object(fake_driver, 'execute')
def test_container_execute_failed(self, mock_execute):
container = Container(self.context, **utils.get_test_container())
@ -433,22 +336,13 @@ class TestManager(base.TestCase):
@mock.patch.object(fake_driver, 'kill')
def test_container_kill(self, mock_kill):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.container_kill(self.context, container, None)
self.compute_manager._do_container_kill(self.context, container, None)
mock_kill.assert_called_once_with(container, None)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_kill_invalid_state(self, mock_validate):
container = Container(self.context, **utils.get_test_container())
mock_validate.side_effect = exception.InvalidStateException
self.assertRaises(exception.InvalidStateException,
self.compute_manager.container_kill,
self.context, container, None)
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'kill')
def test_container_kill_failed(self, mock_kill, mock_validate):
def test_container_kill_failed(self, mock_kill):
container = Container(self.context, **utils.get_test_container())
mock_kill.side_effect = exception.DockerError
self.assertRaises(exception.DockerError,
self.compute_manager.container_kill,
self.context, container, None)
self.compute_manager._do_container_kill,
self.context, container, None, reraise=True)