Add support for 'zun run' command at server side

This patch adds support for 'zun run' command at
server side, for implementing this 'run' method is
implemented in container controller. REST api endpoint
to access this method would be '/v1/containers/run'.

Closes-Bug: #1627589
Change-Id: I2fe69961e3b7ea227dc0ce4ed257de7c680161da
This commit is contained in:
Pradeep Kumar Singh 2016-11-11 13:28:35 +00:00
parent 1cd691f76f
commit c3425ce83f
6 changed files with 210 additions and 33 deletions

View File

@ -18,6 +18,7 @@
"container:logs": "rule:admin_or_user",
"container:execute": "rule:admin_or_user",
"container:kill": "rule:admin_or_user",
"container:run": "rule:default",
"image:create": "rule:default",
"image:get_all": "rule:default",

View File

@ -217,7 +217,8 @@ class ContainersController(rest.RestController):
'unpause': ['POST'],
'logs': ['GET'],
'execute': ['POST'],
'kill': ['POST']
'kill': ['POST'],
'run': ['POST']
}
@pecan.expose('json')
@ -314,6 +315,35 @@ class ContainersController(rest.RestController):
pecan.response.status = 202
return Container.convert_with_links(new_container)
@pecan.expose('json')
@api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception
def run(self, **container_dict):
"""Create and starts a new container.
:param container: a container within the request body.
"""
context = pecan.request.context
policy.enforce(context, "container:run",
action="container:run")
container_dict = Container(**container_dict).as_dict()
container_dict['project_id'] = context.project_id
container_dict['user_id'] = context.user_id
name = container_dict.get('name') or \
self._generate_name_for_container()
container_dict['name'] = name
container_dict['status'] = fields.ContainerStatus.CREATING
new_container = objects.Container(context, **container_dict)
new_container.create(context)
container = pecan.request.rpcapi.container_run(context,
new_container)
# Set the HTTP Location Header
pecan.response.location = link.build_url('containers',
container.uuid)
pecan.response.status = 200
return Container.convert_with_links(container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def patch(self, container_id, **kwargs):

View File

@ -37,6 +37,9 @@ class API(rpc_service.API):
def container_create(self, context, container):
return self._cast('container_create', container=container)
def container_run(self, context, container):
return self._call('container_run', container=container)
def container_delete(self, context, container, force):
return self._call('container_delete', container=container, force=force)

View File

@ -15,6 +15,7 @@
import six
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from zun.common import exception
@ -62,7 +63,17 @@ class Manager(object):
def container_create(self, context, container):
utils.spawn_n(self._do_container_create, context, container)
def _do_container_create(self, context, container):
@translate_exception
def container_run(self, context, container):
return self._do_container_run(context, container)
def _do_container_run(self, context, container):
created_container = self._do_container_create(context,
container,
reraise=True)
return self._do_container_start(context, created_container)
def _do_container_create(self, context, container, reraise=False):
LOG.debug('Creating container...', context=context,
container=container)
@ -75,33 +86,64 @@ class Manager(object):
image = image_driver.pull_image(context, repo,
tag, image_pull_policy)
except exception.ImageNotFound as e:
LOG.error(six.text_type(e))
self._fail_container(container, six.text_type(e))
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(six.text_type(e))
self._fail_container(container, six.text_type(e))
return
except exception.DockerError as e:
LOG.error(_LE("Error occured while calling docker image API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occured while calling docker image API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
return
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
self._fail_container(container, six.text_type(e))
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))
return
container.task_state = fields.TaskState.CONTAINER_CREATING
container.save()
try:
container = self.driver.create(container, image)
except exception.DockerError as e:
LOG.error(_LE("Error occured while calling docker create 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))
finally:
container.task_state = None
container.save()
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occured while calling docker create API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
return
except Exception as e:
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))
return
def _do_container_start(self, context, container):
LOG.debug('Starting container...', context=context,
container=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 occured while calling docker start API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
raise
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), str(e))
self._fail_container(container, six.text_type(e))
raise
@translate_exception
def container_delete(self, context, container, force):
@ -186,20 +228,7 @@ class Manager(object):
@translate_exception
def container_start(self, context, container):
LOG.debug('Starting container...', context=context,
container=container.uuid)
try:
self._validate_container_state(container, 'start')
container = self.driver.start(container)
container.save()
return container
except exception.DockerError as e:
LOG.error(_LE("Error occured while calling docker start API: %s"),
six.text_type(e))
raise
except Exception as e:
LOG.exception(_LE("Unexpected exception: %s"), str(e))
raise e
return self._do_container_start(context, container)
@translate_exception
def container_pause(self, context, container):

View File

@ -23,6 +23,20 @@ from zun.tests.unit.objects import utils as obj_utils
class TestContainerController(api_base.FunctionalTest):
@patch('zun.compute.api.API.container_run')
def test_run_container(self, mock_container_run):
mock_container_run.side_effect = lambda x, y: y
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"environment": {"key1": "val1", "key2": "val2"}}')
response = self.app.post('/v1/containers/run',
params=params,
content_type='application/json')
self.assertEqual(200, response.status_int)
self.assertTrue(mock_container_run.called)
@patch('zun.compute.api.API.container_create')
def test_create_container(self, mock_container_create):
mock_container_create.side_effect = lambda x, y: y

View File

@ -115,6 +115,101 @@ class TestManager(base.TestCase):
self.compute_manager._do_container_create(self.context, container)
mock_fail.assert_called_once_with(container, "Creation Failed")
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(fake_driver, 'create')
@mock.patch.object(fake_driver, 'start')
def test_container_run(self, mock_start,
mock_create, mock_pull, mock_save):
container = Container(self.context, **utils.get_test_container())
mock_pull.return_value = 'fake_path'
mock_create.return_value = container
container.status = 'Stopped'
self.compute_manager.container_run(self.context, container)
mock_save.assert_called_with()
mock_pull.assert_called_once_with(self.context,
container.image,
'latest', 'always')
mock_create.assert_called_once_with(container, 'fake_path')
mock_start.assert_called_once_with(container)
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(manager.Manager, '_fail_container')
def test_container_run_image_not_found(self, mock_fail,
mock_pull, mock_save):
container = Container(self.context, **utils.get_test_container())
mock_pull.side_effect = exception.ImageNotFound(
message="Image Not Found")
with self.assertRaisesRegexp(exception.ImageNotFound,
'Image Not Found'):
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Image Not Found')
mock_pull.assert_called_once_with(self.context,
container.image,
'latest', 'always')
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(manager.Manager, '_fail_container')
def test_container_run_image_pull_exception_raised(self, mock_fail,
mock_pull, mock_save):
container = Container(self.context, **utils.get_test_container())
mock_pull.side_effect = exception.ZunException(
message="Image Not Found")
with self.assertRaisesRegexp(exception.ZunException,
'Image Not Found'):
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Image Not Found')
mock_pull.assert_called_once_with(self.context,
container.image,
'latest', 'always')
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(manager.Manager, '_fail_container')
def test_container_run_image_pull_docker_error(self, mock_fail,
mock_pull, mock_save):
container = Container(self.context, **utils.get_test_container())
mock_pull.side_effect = exception.DockerError(
message="Docker Error occurred")
with self.assertRaisesRegexp(exception.DockerError,
'Docker Error occurred'):
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Docker Error occurred')
mock_pull.assert_called_once_with(self.context,
container.image,
'latest', 'always')
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(manager.Manager, '_fail_container')
@mock.patch.object(fake_driver, 'create')
def test_container_run_create_raises_docker_error(self, mock_create,
mock_fail,
mock_pull, mock_save):
container = Container(self.context, **utils.get_test_container())
mock_pull.return_value = {'name': 'nginx', 'path': None}
mock_create.side_effect = exception.DockerError(
message="Docker Error occurred")
with self.assertRaisesRegexp(exception.DockerError,
'Docker Error occurred'):
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Docker Error occurred')
mock_pull.assert_called_once_with(self.context,
container.image,
'latest', 'always')
mock_create.assert_called_once_with(container,
{'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):
@ -221,21 +316,26 @@ class TestManager(base.TestCase):
mock_start.assert_called_once_with(container)
@mock.patch.object(manager.Manager, '_validate_container_state')
def test_container_start_invalid_state(self, mock_validate):
@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_validate):
def test_container_start_failed(self, mock_start,
mock_fail, mock_validate):
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)
mock_fail.assert_called_once()
@mock.patch.object(manager.Manager, '_validate_container_state')
@mock.patch.object(fake_driver, 'pause')