From 30eacb52c965e3107616431d7567ddb86d3dc080 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Wed, 7 Oct 2015 14:09:40 -0400 Subject: [PATCH] Pull metrics by using TLS enabled client Magnum introduced TLS support in swarm bay, so swarm monitor needs to use TLS enabled client to pull data. In addition, this commit eliminates data pulling from the docker daemon of individual bay nodes, since they are not secured by TLS. Instead, we always pull from swarm. Due to that, additional text parsing is needed, since the output of docker.info from swarm is not well formed. Change-Id: I121bfa98622c240d5c777901794a35c0d8990345 Closes-Bug: #1503460 --- magnum/common/docker_utils.py | 6 ++ magnum/conductor/monitors.py | 92 ++++++++++++-------- magnum/tests/unit/conductor/test_monitors.py | 26 +++--- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/magnum/common/docker_utils.py b/magnum/common/docker_utils.py index 77f784db65..1f42cb8a2e 100644 --- a/magnum/common/docker_utils.py +++ b/magnum/common/docker_utils.py @@ -73,6 +73,12 @@ def docker_for_container(context, container): if magnum_utils.is_uuid_like(container): container = objects.Container.get_by_uuid(context, container) bay = conductor_utils.retrieve_bay(context, container) + with docker_for_bay(context, bay) as docker: + yield docker + + +@contextlib.contextmanager +def docker_for_bay(context, bay): baymodel = conductor_utils.retrieve_baymodel(context, bay) tcp_url = 'tcp://%s:2376' % bay.api_address diff --git a/magnum/conductor/monitors.py b/magnum/conductor/monitors.py index 9736af574a..be30ae508e 100644 --- a/magnum/conductor/monitors.py +++ b/magnum/conductor/monitors.py @@ -19,7 +19,7 @@ from oslo_config import cfg from oslo_log import log import six -from magnum.conductor.handlers.common import docker_client +from magnum.common import docker_utils from magnum.i18n import _LW from magnum import objects from magnum.objects.fields import BayType as bay_type @@ -39,7 +39,8 @@ CONF.import_opt('default_timeout', @six.add_metaclass(abc.ABCMeta) class MonitorBase(object): - def __init__(self, bay): + def __init__(self, context, bay): + self.context = context self.bay = bay @abc.abstractproperty @@ -64,8 +65,8 @@ class MonitorBase(object): class SwarmMonitor(MonitorBase): - def __init__(self, bay): - super(SwarmMonitor, self).__init__(bay) + def __init__(self, context, bay): + super(SwarmMonitor, self).__init__(context, bay) self.data = {} self.data['nodes'] = [] self.data['containers'] = [] @@ -80,27 +81,23 @@ class SwarmMonitor(MonitorBase): } def pull_data(self): - # pull data from each bay node - nodes = [] - for node_addr in (self.bay.node_addresses + [self.bay.api_address]): - docker = self._docker_client(node_addr) - node_info = docker.info() - nodes.append(node_info) - self.data['nodes'] = nodes + with docker_utils.docker_for_bay(self.context, + self.bay) as docker: + system_info = docker.info() + self.data['nodes'] = self._parse_node_info(system_info) - # pull data from each container - containers = [] - docker = self._docker_swarm_client(self.bay) - for container in docker.containers(all=True): - try: - container = docker.inspect_container(container['Id']) - except Exception as e: - LOG.warn(_LW("Ignore error [%(e)s] when inspecting container " - "%(container_id)s."), - {'e': e, 'container_id': container['Id']}, - exc_info=True) - containers.append(container) - self.data['containers'] = containers + # pull data from each container + containers = [] + for container in docker.containers(all=True): + try: + container = docker.inspect_container(container['Id']) + except Exception as e: + LOG.warn(_LW("Ignore error [%(e)s] when inspecting " + "container %(container_id)s."), + {'e': e, 'container_id': container['Id']}, + exc_info=True) + containers.append(container) + self.data['containers'] = containers def compute_memory_util(self): mem_total = 0 @@ -115,22 +112,49 @@ class SwarmMonitor(MonitorBase): else: return mem_reserved * 100 / mem_total - def _docker_client(self, api_address, port=2375): - tcp_url = 'tcp://%s:%s' % (api_address, port) - return docker_client.DockerHTTPClient( - tcp_url, - CONF.docker.docker_remote_api_version, - CONF.docker.default_timeout - ) + def _parse_node_info(self, system_info): + """Parse system_info to retrieve memory size of each node. - def _docker_swarm_client(self, bay): - return self._docker_client(bay.api_address, port=2376) + :param system_info: The output returned by docker.info(). Example: + { + u'Debug': False, + u'NEventsListener': 0, + u'DriverStatus': [ + [u'\x08Strategy', u'spread'], + [u'\x08Filters', u'...'], + [u'\x08Nodes', u'2'], + [u'node1', u'10.0.0.4:2375'], + [u' \u2514 Containers', u'1'], + [u' \u2514 Reserved CPUs', u'0 / 1'], + [u' \u2514 Reserved Memory', u'0 B / 2.052 GiB'], + [u'node2', u'10.0.0.3:2375'], + [u' \u2514 Containers', u'2'], + [u' \u2514 Reserved CPUs', u'0 / 1'], + [u' \u2514 Reserved Memory', u'0 B / 2.052 GiB'] + ], + u'Containers': 3 + } + :return: Memory size of each node. Excample: + [{'MemTotal': 2203318222.848}, + {'MemTotal': 2203318222.848}] + """ + nodes = [] + for info in system_info['DriverStatus']: + key = info[0] + value = info[1] + if key == u' \u2514 Reserved Memory': + memory = value # Example: '0 B / 2.052 GiB' + memory = memory.split('/')[1].strip() # Example: '2.052 GiB' + memory = memory.split(' ')[0] # Example: '2.052' + memory = float(memory) * 1024 * 1024 * 1024 + nodes.append({'MemTotal': memory}) + return nodes def create_monitor(context, bay): baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id) if baymodel.coe == bay_type.SWARM: - return SwarmMonitor(bay) + return SwarmMonitor(context, bay) # TODO(hongbin): add support for other bay types LOG.debug("Cannot create monitor with bay type '%s'" % baymodel.coe) diff --git a/magnum/tests/unit/conductor/test_monitors.py b/magnum/tests/unit/conductor/test_monitors.py index 392a5be428..62d0adfedf 100644 --- a/magnum/tests/unit/conductor/test_monitors.py +++ b/magnum/tests/unit/conductor/test_monitors.py @@ -40,7 +40,7 @@ class MonitorsTestCase(base.TestCase): bay = utils.get_test_bay(node_addresses=['1.2.3.4'], api_address='5.6.7.8') self.bay = objects.Bay(self.context, **bay) - self.monitor = monitors.SwarmMonitor(self.bay) + self.monitor = monitors.SwarmMonitor(self.context, self.bay) p = mock.patch('magnum.conductor.monitors.SwarmMonitor.metrics_spec', new_callable=mock.PropertyMock) self.mock_metrics_spec = p.start() @@ -63,35 +63,35 @@ class MonitorsTestCase(base.TestCase): monitor = monitors.create_monitor(self.context, self.bay) self.assertIsNone(monitor) - @mock.patch('magnum.conductor.handlers.common.docker_client.' - 'DockerHTTPClient') - def test_swarm_monitor_pull_data_success(self, mock_docker_http_client): + @mock.patch('magnum.common.docker_utils.docker_for_bay') + def test_swarm_monitor_pull_data_success(self, mock_docker_for_bay): mock_docker = mock.MagicMock() - mock_docker.info.return_value = 'test_node' + mock_docker.info.return_value = {'DriverStatus': [[ + u' \u2514 Reserved Memory', u'0 B / 1 GiB']]} mock_docker.containers.return_value = [mock.MagicMock()] mock_docker.inspect_container.return_value = 'test_container' - mock_docker_http_client.return_value = mock_docker + mock_docker_for_bay.return_value.__enter__.return_value = mock_docker self.monitor.pull_data() self.assertEqual(self.monitor.data['nodes'], - ['test_node', 'test_node']) + [{'MemTotal': 1073741824.0}]) self.assertEqual(self.monitor.data['containers'], ['test_container']) - @mock.patch('magnum.conductor.handlers.common.docker_client.' - 'DockerHTTPClient') - def test_swarm_monitor_pull_data_raise(self, mock_docker_http_client): + @mock.patch('magnum.common.docker_utils.docker_for_bay') + def test_swarm_monitor_pull_data_raise(self, mock_docker_for_bay): mock_container = mock.MagicMock() mock_docker = mock.MagicMock() - mock_docker.info.return_value = 'test_node' + mock_docker.info.return_value = {'DriverStatus': [[ + u' \u2514 Reserved Memory', u'0 B / 1 GiB']]} mock_docker.containers.return_value = [mock_container] mock_docker.inspect_container.side_effect = Exception("inspect error") - mock_docker_http_client.return_value = mock_docker + mock_docker_for_bay.return_value.__enter__.return_value = mock_docker self.monitor.pull_data() self.assertEqual(self.monitor.data['nodes'], - ['test_node', 'test_node']) + [{'MemTotal': 1073741824.0}]) self.assertEqual(self.monitor.data['containers'], [mock_container]) def test_swarm_monitor_get_metric_names(self):