Add support for exec resize
This endpoint will be used by cleint to resize the tty session of exec. Partial-Implements: blueprint support-interactive-mode Change-Id: Ied3b90295c75a32f8543a4e92481a01c22b870df
This commit is contained in:
parent
ab81332458
commit
cd49efcc90
@ -17,6 +17,7 @@
|
||||
"container:unpause": "rule:admin_or_user",
|
||||
"container:logs": "rule:admin_or_user",
|
||||
"container:execute": "rule:admin_or_user",
|
||||
"container:execute_resize": "rule:admin_or_user",
|
||||
"container:kill": "rule:admin_or_user",
|
||||
"container:update": "rule:admin_or_user",
|
||||
"container:rename": "rule:admin_or_user",
|
||||
|
@ -86,6 +86,7 @@ class ContainersController(rest.RestController):
|
||||
'unpause': ['POST'],
|
||||
'logs': ['GET'],
|
||||
'execute': ['POST'],
|
||||
'execute_resize': ['POST'],
|
||||
'kill': ['POST'],
|
||||
'rename': ['POST'],
|
||||
'attach': ['GET'],
|
||||
@ -427,6 +428,21 @@ class ContainersController(rest.RestController):
|
||||
return compute_api.container_exec(context, container, kw['command'],
|
||||
run, interactive)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request,
|
||||
schema.query_param_execute_resize)
|
||||
def execute_resize(self, container_id, exec_id, **kw):
|
||||
container = _get_container(container_id)
|
||||
check_policy_on_container(container.as_dict(),
|
||||
"container:execute_resize")
|
||||
utils.validate_container_state(container, 'execute_resize')
|
||||
LOG.debug('Calling tty resize used by exec %s', exec_id)
|
||||
context = pecan.request.context
|
||||
compute_api = pecan.request.compute_api
|
||||
return compute_api.container_exec_resize(
|
||||
context, container, exec_id, kw.get('h', None), kw.get('w', None))
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def kill(self, container_id, **kw):
|
||||
|
@ -109,3 +109,13 @@ query_param_resize = {
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
query_param_execute_resize = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'exec_id': parameter_types.exec_id,
|
||||
'h': parameter_types.positive_integer,
|
||||
'w': parameter_types.positive_integer
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ VALID_STATES = {
|
||||
'unpause': ['Paused'],
|
||||
'kill': ['Running'],
|
||||
'execute': ['Running'],
|
||||
'execute_resize': ['Running'],
|
||||
'update': ['Running', 'Stopped', 'Paused', 'Created'],
|
||||
'attach': ['Running'],
|
||||
'resize': ['Running'],
|
||||
|
@ -151,3 +151,10 @@ logs_since = {
|
||||
'pattern': '(^[0-9]*$)|\
|
||||
([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{1,3})'
|
||||
}
|
||||
|
||||
exec_id = {
|
||||
'type': 'string',
|
||||
'maxLength': 64,
|
||||
'minLength': 64,
|
||||
'pattern': '^[a-f0-9]*$'
|
||||
}
|
||||
|
@ -85,6 +85,9 @@ class API(object):
|
||||
def container_exec(self, context, container, *args):
|
||||
return self.rpcapi.container_exec(context, container, *args)
|
||||
|
||||
def container_exec_resize(self, context, container, *args):
|
||||
return self.rpcapi.container_exec_resize(context, container, *args)
|
||||
|
||||
def container_kill(self, context, container, *args):
|
||||
return self.rpcapi.container_kill(context, container, *args)
|
||||
|
||||
|
@ -347,6 +347,19 @@ class Manager(object):
|
||||
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||
raise
|
||||
|
||||
@translate_exception
|
||||
def container_exec_resize(self, context, exec_id, height, width):
|
||||
LOG.debug('Resizing the tty session used by the exec: %s', exec_id)
|
||||
try:
|
||||
return self.driver.execute_resize(exec_id, height, width)
|
||||
except exception.DockerError as e:
|
||||
LOG.error("Error occurred while calling Docker exec API: %s",
|
||||
six.text_type(e))
|
||||
raise
|
||||
except Exception as e:
|
||||
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||
raise
|
||||
|
||||
def _do_container_kill(self, context, container, signal, reraise=False):
|
||||
LOG.debug('kill signal to container: %s', container.uuid)
|
||||
try:
|
||||
|
@ -80,6 +80,11 @@ class API(rpc_service.API):
|
||||
container=container, command=command, run=run,
|
||||
interactive=interactive)
|
||||
|
||||
def container_exec_resize(self, context, container, exec_id, height,
|
||||
width):
|
||||
return self._call(container.host, 'container_exec_resize',
|
||||
exec_id=exec_id, height=height, width=width)
|
||||
|
||||
def container_kill(self, context, container, signal):
|
||||
self._cast(container.host, 'container_kill', container=container,
|
||||
signal=signal)
|
||||
|
@ -302,6 +302,18 @@ class DockerDriver(driver.ContainerDriver):
|
||||
inspect_res = docker.exec_inspect(exec_id)
|
||||
return {"output": output, "exit_code": inspect_res['ExitCode']}
|
||||
|
||||
def execute_resize(self, exec_id, height, width):
|
||||
height = int(height)
|
||||
width = int(width)
|
||||
with docker_utils.docker_client() as docker:
|
||||
try:
|
||||
docker.exec_resize(exec_id, height=height, width=width)
|
||||
except errors.APIError as api_error:
|
||||
if '404' in str(api_error):
|
||||
raise exception.Invalid(_(
|
||||
"no such exec instance: %s") % str(api_error))
|
||||
raise
|
||||
|
||||
@check_container_id
|
||||
def kill(self, container, signal=None):
|
||||
with docker_utils.docker_client() as docker:
|
||||
@ -350,7 +362,7 @@ class DockerDriver(driver.ContainerDriver):
|
||||
return url
|
||||
|
||||
@check_container_id
|
||||
def resize(self, container, height=None, width=None):
|
||||
def resize(self, container, height, width):
|
||||
with docker_utils.docker_client() as docker:
|
||||
height = int(height)
|
||||
width = int(width)
|
||||
|
@ -108,6 +108,10 @@ class ContainerDriver(object):
|
||||
"""Run the command specified by an execute instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def execute_resize(self, exec_id, height, width):
|
||||
"""Resizes the tty session used by the exec."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def kill(self, container, signal):
|
||||
"""kill signal to a container."""
|
||||
raise NotImplementedError()
|
||||
|
@ -1230,3 +1230,22 @@ class TestContainerEnforcement(api_base.FunctionalTest):
|
||||
self._owner_check('container:%s' % action, self.post_json,
|
||||
'/containers/%s/%s/' % (container.uuid, action),
|
||||
{}, expect_errors=True)
|
||||
|
||||
@patch('zun.common.utils.validate_container_state')
|
||||
@patch('zun.compute.api.API.container_exec_resize')
|
||||
@patch('zun.api.utils.get_resource')
|
||||
def test_execute_resize_container_exec(
|
||||
self, mock_get_resource, mock_exec_resize, mock_validate):
|
||||
test_container = utils.get_test_container()
|
||||
test_container_obj = objects.Container(self.context, **test_container)
|
||||
mock_get_resource.return_value = test_container_obj
|
||||
container_name = test_container.get('name')
|
||||
url = '/v1/containers/%s/%s/' % (container_name, 'execute_resize')
|
||||
fake_exec_id = ('7df36611fa1fc855618c2c643835d41d'
|
||||
'ac3fe568e7688f0bae66f7bcb3cccc6c')
|
||||
kwargs = {'exec_id': fake_exec_id, 'h': '100', 'w': '100'}
|
||||
response = self.app.post(url, kwargs)
|
||||
self.assertEqual(200, response.status_int)
|
||||
mock_exec_resize.assert_called_once_with(
|
||||
mock.ANY, test_container_obj, fake_exec_id, kwargs['h'],
|
||||
kwargs['w'])
|
||||
|
@ -500,3 +500,16 @@ class TestManager(base.TestCase):
|
||||
mock_save.assert_called_once()
|
||||
mock_inspect.assert_called_once_with(repo_tag)
|
||||
mock_load.assert_called_once_with(repo_tag, ret['path'])
|
||||
|
||||
@mock.patch.object(fake_driver, 'execute_resize')
|
||||
def test_container_exec_resize(self, mock_resize):
|
||||
self.compute_manager.container_exec_resize(
|
||||
self.context, 'fake_exec_id', "100", "100")
|
||||
mock_resize.assert_called_once_with('fake_exec_id', "100", "100")
|
||||
|
||||
@mock.patch.object(fake_driver, 'execute_resize')
|
||||
def test_container_exec_resize_failed(self, mock_resize):
|
||||
mock_resize.side_effect = exception.DockerError
|
||||
self.assertRaises(exception.DockerError,
|
||||
self.compute_manager.container_exec_resize,
|
||||
self.context, 'fake_exec_id', "100", "100")
|
||||
|
@ -405,6 +405,13 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
self.assertEqual(result_addresses,
|
||||
{'default': [{'addr': '127.0.0.1', }, ], })
|
||||
|
||||
def test_execute_resize(self):
|
||||
self.mock_docker.exec_resize = mock.Mock()
|
||||
fake_exec_id = 'fake_id'
|
||||
self.driver.execute_resize(fake_exec_id, "100", "100")
|
||||
self.mock_docker.exec_resize.assert_called_once_with(
|
||||
fake_exec_id, height=100, width=100)
|
||||
|
||||
|
||||
class TestNovaDockerDriver(base.DriverTestCase):
|
||||
def setUp(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user