From 65d854d48aa0dcb4382da4fd1dd18fba404821d9 Mon Sep 17 00:00:00 2001 From: miaohb Date: Thu, 11 May 2017 19:34:22 +0800 Subject: [PATCH] Server support for display snapshot of zun stats This patch adds support for display snapshot of zun stats at server side. Implements: blueprint support-stats-snapshot Change-Id: Id82a4f9f7b9e32d202852083da5714e42751fe98 --- etc/zun/policy.json | 1 + zun/api/controllers/v1/containers.py | 13 +++++++++++++ zun/common/utils.py | 1 + zun/compute/api.py | 3 +++ zun/compute/manager.py | 13 +++++++++++++ zun/compute/rpcapi.py | 5 +++++ zun/container/docker/driver.py | 6 ++++++ zun/container/driver.py | 4 ++++ .../unit/api/controllers/v1/test_containers.py | 17 +++++++++++++++++ 9 files changed, 63 insertions(+) diff --git a/etc/zun/policy.json b/etc/zun/policy.json index 1a7f0b6a1..0ed01bacf 100644 --- a/etc/zun/policy.json +++ b/etc/zun/policy.json @@ -26,6 +26,7 @@ "container:top": "rule:admin_or_user", "container:get_archive": "rule:admin_or_user", "container:put_archive": "rule:admin_or_user", + "container:stats": "rule:admin_or_user", "image:pull": "rule:default", "image:get_all": "rule:default", diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index cf6c53caa..3ffbe3cb6 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -93,6 +93,7 @@ class ContainersController(base.Controller): 'top': ['GET'], 'get_archive': ['GET'], 'put_archive': ['POST'], + 'stats': ['GET'], } @pecan.expose('json') @@ -528,3 +529,15 @@ class ContainersController(base.Controller): compute_api = pecan.request.compute_api compute_api.container_put_archive(context, container, kw['path'], kw['data']) + + @pecan.expose('json') + @exception.wrap_pecan_controller_exception + def stats(self, container_id): + container = _get_container(container_id) + check_policy_on_container(container.as_dict(), "container:stats") + utils.validate_container_state(container, 'stats') + LOG.debug('Calling compute.container_stats with %s' + % (container.uuid)) + context = pecan.request.context + compute_api = pecan.request.compute_api + return compute_api.container_stats(context, container) diff --git a/zun/common/utils.py b/zun/common/utils.py index 2f4daa1cd..a368d4e4c 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -57,6 +57,7 @@ VALID_STATES = { consts.STOPPED], 'logs': [consts.CREATED, consts.ERROR, consts.PAUSED, consts.RUNNING, consts.STOPPED, consts.UNKNOWN], + 'stats': [consts.RUNNING], } diff --git a/zun/compute/api.py b/zun/compute/api.py index 81b44f5b4..64153d4ef 100644 --- a/zun/compute/api.py +++ b/zun/compute/api.py @@ -109,6 +109,9 @@ class API(object): def container_put_archive(self, context, container, *args): return self.rpcapi.container_put_archive(context, container, *args) + def container_stats(self, context, container): + return self.rpcapi.container_stats(context, container) + def image_pull(self, context, image, *args): return self.rpcapi.image_pull(context, image, *args) diff --git a/zun/compute/manager.py b/zun/compute/manager.py index d4d2557c8..d21c498be 100755 --- a/zun/compute/manager.py +++ b/zun/compute/manager.py @@ -462,6 +462,19 @@ class Manager(object): LOG.exception("Unexpected exception: %s", six.text_type(e)) raise + @translate_exception + def container_stats(self, context, container): + LOG.debug('Displaying stats of the container: %s', container.uuid) + try: + return self.driver.stats(container) + except exception.DockerError as e: + LOG.error("Error occurred while calling Docker stats API: %s", + six.text_type(e)) + raise + except Exception as e: + LOG.exception("Unexpected exception: %s", six.text_type(e)) + raise + def image_pull(self, context, image): utils.spawn_n(self._do_image_pull, context, image) diff --git a/zun/compute/rpcapi.py b/zun/compute/rpcapi.py index b819fc6db..fcd2f3fc4 100644 --- a/zun/compute/rpcapi.py +++ b/zun/compute/rpcapi.py @@ -141,6 +141,11 @@ class API(rpc_service.API): return self._call(container.host, 'container_put_archive', container=container, path=path, data=data) + @check_container_host + def container_stats(self, context, container): + return self._call(container.host, 'container_stats', + container=container) + 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 diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index 6583904b0..ceeaa0589 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -463,6 +463,12 @@ class DockerDriver(driver.ContainerDriver): with docker_utils.docker_client() as docker: docker.put_archive(container.container_id, path, data) + @check_container_id + def stats(self, container): + with docker_utils.docker_client() as docker: + return docker.stats(container.container_id, decode=False, + stream=False) + def _encode_utf8(self, value): if six.PY2 and not isinstance(value, unicode): value = unicode(value) diff --git a/zun/container/driver.py b/zun/container/driver.py index d8964a100..744e15bbe 100644 --- a/zun/container/driver.py +++ b/zun/container/driver.py @@ -141,6 +141,10 @@ class ContainerDriver(object): """copy resource to a container.""" raise NotImplementedError() + def stats(self, container): + """Display stats of the container(s).""" + raise NotImplementedError() + def create_sandbox(self, context, container, **kwargs): """Create a sandbox.""" raise NotImplementedError() diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index 10f1a1608..def5402e2 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -1197,6 +1197,23 @@ class TestContainerController(api_base.FunctionalTest): self.app.post('/v1/containers/%s/%s/' % (test_object.uuid, 'put_archive')) + @patch('zun.common.utils.validate_container_state') + @patch('zun.compute.api.API.container_stats') + @patch('zun.objects.Container.get_by_uuid') + def test_stats_container_by_uuid(self, mock_get_by_uuid, + mock_container_stats, mock_validate): + mock_container_stats.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') + url = '/v1/containers/%s/stats'\ + % container_uuid + response = self.app.get(url) + self.assertEqual(200, response.status_int) + self.assertTrue(mock_container_stats.called) + class TestContainerEnforcement(api_base.FunctionalTest):