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:
Feng Shengqin 2017-02-11 01:17:27 +08:00
parent 0d2e77e0a5
commit 0e95d55607
11 changed files with 113 additions and 0 deletions

View File

@ -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",

View File

@ -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)

View File

@ -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 = {

View File

@ -47,6 +47,7 @@ VALID_STATES = {
'update': ['Running', 'Stopped', 'Paused'],
'attach': ['Running'],
'resize': ['Running'],
'top': ['Running'],
}

View File

@ -136,3 +136,8 @@ restart_policy = {
"additionalProperties": False,
"required": ['Name']
}
string_ps_args = {
'type': ['string'],
'pattern': '[a-zA-Z- ,+]*'
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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):