Support the command "zun top"
The BP adds "zun top" which can display the running progresses inside the container. Change-Id: I55524598e220e6f227ab789901df9d49fa2357f7 Implements: blueprint support-zun-top
This commit is contained in:
parent
0d2e77e0a5
commit
0e95d55607
@ -22,6 +22,7 @@
|
||||
"container:rename": "rule:admin_or_user",
|
||||
"container:attach": "rule:admin_or_user",
|
||||
"container:resize": "rule:admin_or_user",
|
||||
"container:top": "rule:admin_or_user",
|
||||
|
||||
"image:pull": "rule:default",
|
||||
"image:get_all": "rule:default",
|
||||
|
@ -90,6 +90,7 @@ class ContainersController(rest.RestController):
|
||||
'rename': ['POST'],
|
||||
'attach': ['GET'],
|
||||
'resize': ['POST'],
|
||||
'top': ['GET']
|
||||
}
|
||||
|
||||
@pecan.expose('json')
|
||||
@ -447,3 +448,16 @@ class ContainersController(rest.RestController):
|
||||
compute_api = pecan.request.compute_api
|
||||
compute_api.container_resize(context, container, kw.get('h', None),
|
||||
kw.get('w', None))
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request, schema.query_param_top)
|
||||
def top(self, container_id, ps_args=None):
|
||||
container = _get_container(container_id)
|
||||
check_policy_on_container(container.as_dict(), "container:top")
|
||||
utils.validate_container_state(container, 'top')
|
||||
LOG.debug('Calling compute.container_top with %s' %
|
||||
container.uuid)
|
||||
context = pecan.request.context
|
||||
compute_api = pecan.request.compute_api
|
||||
return compute_api.container_top(context, container, ps_args)
|
||||
|
@ -89,6 +89,13 @@ query_param_logs = {
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
query_param_top = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'ps_args': parameter_types.string_ps_args
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
query_param_stop = copy.deepcopy(query_param_reboot)
|
||||
|
||||
query_param_resize = {
|
||||
|
@ -47,6 +47,7 @@ VALID_STATES = {
|
||||
'update': ['Running', 'Stopped', 'Paused'],
|
||||
'attach': ['Running'],
|
||||
'resize': ['Running'],
|
||||
'top': ['Running'],
|
||||
}
|
||||
|
||||
|
||||
|
@ -136,3 +136,8 @@ restart_policy = {
|
||||
"additionalProperties": False,
|
||||
"required": ['Name']
|
||||
}
|
||||
|
||||
string_ps_args = {
|
||||
'type': ['string'],
|
||||
'pattern': '[a-zA-Z- ,+]*'
|
||||
}
|
||||
|
@ -93,6 +93,9 @@ class API(object):
|
||||
def container_resize(self, context, container, *args):
|
||||
return self.rpcapi.container_resize(context, container, *args)
|
||||
|
||||
def container_top(self, context, container, *args):
|
||||
return self.rpcapi.container_top(context, container, *args)
|
||||
|
||||
def image_pull(self, context, image, *args):
|
||||
return self.rpcapi.image_pull(context, image, *args)
|
||||
|
||||
|
@ -378,6 +378,20 @@ class Manager(object):
|
||||
raise
|
||||
return container
|
||||
|
||||
@translate_exception
|
||||
def container_top(self, context, container, ps_args):
|
||||
LOG.debug('Displaying the running processes inside the container: %s',
|
||||
container.uuid)
|
||||
try:
|
||||
return self.driver.top(container, ps_args)
|
||||
except exception.DockerError as e:
|
||||
LOG.error(_LE("Error occurred while calling Docker top API: %s"),
|
||||
six.text_type(e))
|
||||
raise
|
||||
except Exception as e:
|
||||
LOG.exception(_LE("Unexpected exception: %s"), six.text_type(e))
|
||||
raise
|
||||
|
||||
def image_pull(self, context, image):
|
||||
utils.spawn_n(self._do_image_pull, context, image)
|
||||
|
||||
|
@ -91,6 +91,10 @@ class API(rpc_service.API):
|
||||
return self._call(container.host, 'container_resize',
|
||||
container=container, height=height, width=width)
|
||||
|
||||
def container_top(self, context, container, ps_args):
|
||||
return self._call(container.host, 'container_top',
|
||||
container=container, ps_args=ps_args)
|
||||
|
||||
def image_pull(self, context, image):
|
||||
# NOTE(hongbin): Image API doesn't support multiple compute nodes
|
||||
# scenario yet, so we temporarily set host to None and rpc will
|
||||
|
@ -318,6 +318,17 @@ class DockerDriver(driver.ContainerDriver):
|
||||
docker.resize(container.container_id, height, width)
|
||||
return container
|
||||
|
||||
@check_container_id
|
||||
def top(self, container, ps_args=None):
|
||||
with docker_utils.docker_client() as docker:
|
||||
try:
|
||||
if ps_args is None or ps_args == 'None':
|
||||
return docker.top(container.container_id)
|
||||
else:
|
||||
return docker.top(container.container_id, ps_args)
|
||||
except errors.APIError:
|
||||
raise
|
||||
|
||||
def _encode_utf8(self, value):
|
||||
if six.PY2 and not isinstance(value, unicode):
|
||||
value = unicode(value)
|
||||
|
@ -115,6 +115,10 @@ class ContainerDriver(object):
|
||||
"""resize tty of a container."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def top(self, container, ps_args):
|
||||
"""display the running progresses inside the container."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_sandbox(self, context, container, **kwargs):
|
||||
"""Create a sandbox."""
|
||||
raise NotImplementedError()
|
||||
|
@ -1270,6 +1270,55 @@ class TestContainerController(api_base.FunctionalTest):
|
||||
(container_uuid, 'resize'), body)
|
||||
self.assertTrue(mock_container_resize.called)
|
||||
|
||||
@patch('zun.common.utils.validate_container_state')
|
||||
@patch('zun.compute.api.API.container_top')
|
||||
@patch('zun.objects.Container.get_by_name')
|
||||
def test_top_command_by_name(self, mock_get_by_name,
|
||||
mock_container_top, mock_validate):
|
||||
mock_container_top.return_value = ""
|
||||
test_container = utils.get_test_container()
|
||||
test_container_obj = objects.Container(self.context, **test_container)
|
||||
mock_get_by_name.return_value = test_container_obj
|
||||
|
||||
container_name = test_container.get('name')
|
||||
response = self.app.get('/v1/containers/%s/top?ps_args=None' %
|
||||
container_name)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertTrue(mock_container_top.called)
|
||||
|
||||
@patch('zun.common.utils.validate_container_state')
|
||||
@patch('zun.compute.api.API.container_top')
|
||||
@patch('zun.objects.Container.get_by_uuid')
|
||||
def test_top_command_by_uuid(self, mock_get_by_uuid,
|
||||
mock_container_top, mock_validate):
|
||||
mock_container_top.return_value = ""
|
||||
test_container = utils.get_test_container()
|
||||
test_container_obj = objects.Container(self.context, **test_container)
|
||||
mock_get_by_uuid.return_value = test_container_obj
|
||||
|
||||
container_uuid = test_container.get('uuid')
|
||||
response = self.app.get('/v1/containers/%s/top?ps_args=aux' %
|
||||
container_uuid)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertTrue(mock_container_top.called)
|
||||
|
||||
@patch('zun.common.utils.validate_container_state')
|
||||
@patch('zun.compute.api.API.container_top')
|
||||
@patch('zun.objects.Container.get_by_uuid')
|
||||
def test_top_command_invalid_ps(self, mock_get_by_uuid,
|
||||
mock_container_top, mock_validate):
|
||||
mock_container_top.return_value = ""
|
||||
test_container = utils.get_test_container()
|
||||
test_container_obj = objects.Container(self.context, **test_container)
|
||||
mock_get_by_uuid.return_value = test_container_obj
|
||||
mock_container_top.side_effect = Exception
|
||||
|
||||
container_uuid = test_container.get('uuid')
|
||||
self.assertRaises(AppError, self.app.get,
|
||||
'/v1/containers/%s/top?ps_args=kkkk' %
|
||||
container_uuid)
|
||||
self.assertTrue(mock_container_top.called)
|
||||
|
||||
|
||||
class TestContainerEnforcement(api_base.FunctionalTest):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user