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:
parent
1cd691f76f
commit
c3425ce83f
@ -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",
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,16 +86,21 @@ class Manager(object):
|
||||
image = image_driver.pull_image(context, repo,
|
||||
tag, image_pull_policy)
|
||||
except exception.ImageNotFound as 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"),
|
||||
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))
|
||||
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
|
||||
|
||||
@ -92,16 +108,42 @@ class Manager(object):
|
||||
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):
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user