Support for Docker API < 2.0 is deprecated (docker plugins)

This commit is contained in:
nicolargo 2017-10-03 22:18:13 +02:00
parent 98716b551b
commit eb3cd8fd20
2 changed files with 56 additions and 125 deletions

4
NEWS
View File

@ -13,6 +13,10 @@ Bugs corrected:
* Failed to connect to bus: No such file or directory #1156
Backward-incompatible changes:
* Support for Docker API < 2.0 is deprecated (docker plugins)
==============================================================================
Glances Version 2
==============================================================================

View File

@ -20,7 +20,6 @@
"""Docker plugin."""
import os
import re
import threading
import time
@ -29,13 +28,10 @@ from glances.logger import logger
from glances.timer import getTimeSinceLastUpdate
from glances.plugins.glances_plugin import GlancesPlugin
from glances.globals import WINDOWS
# Docker-py library (optional and Linux-only)
# https://github.com/docker/docker-py
try:
import docker
import requests
except ImportError as e:
logger.debug("Docker library not found (%s). Glances cannot grab Docker info." % e)
docker_tag = False
@ -47,7 +43,7 @@ class Plugin(GlancesPlugin):
"""Glances Docker plugin.
stats is a list
stats is a dict: {'version': {...}, 'containers': [{}, {}]}
"""
def __init__(self, args=None):
@ -61,7 +57,7 @@ class Plugin(GlancesPlugin):
self.display_curse = True
# Init the Docker API
self.docker_client = False
self.docker_client = self.connect()
# Dict of thread (to grab stats asynchroniously, one thread is created by container)
# key: Container Id
@ -95,79 +91,13 @@ class Plugin(GlancesPlugin):
logger.debug("docker plugin - Docker export error {}".format(e))
return ret
def __connect_old(self, version):
"""Connect to the Docker server with the 'old school' method"""
# Glances is compatible with both API 2.0 and <2.0
# (thanks to the @bacondropped patch)
if hasattr(docker, 'APIClient'):
# Correct issue #1000 for API 2.0
init_docker = docker.APIClient
elif hasattr(docker, 'Client'):
# < API 2.0
init_docker = docker.Client
else:
# Can not found init method (new API version ?)
logger.error("docker plugin - Can not found any way to init the Docker API")
return None
# Init connection to the Docker API
try:
if WINDOWS:
url = 'npipe:////./pipe/docker_engine'
else:
url = 'unix://var/run/docker.sock'
if version is None:
ret = init_docker(base_url=url)
else:
ret = init_docker(base_url=url,
version=version)
except NameError:
# docker lib not found
return None
return ret
def connect(self, version=None):
def connect(self):
"""Connect to the Docker server."""
if hasattr(docker, 'from_env') and version is not None:
# Connect to Docker using the default socket or
# the configuration in your environment
ret = docker.from_env()
else:
ret = self.__connect_old(version=version)
global docker_tag
# Check the server connection with the version() method
try:
ret.version()
except requests.exceptions.ConnectionError as e:
# Connexion error (Docker not detected)
# Let this message in debug mode
logger.debug("docker plugin - Can't connect to the Docker server (%s)" % e)
return None
except docker.errors.APIError as e:
if version is None:
# API error (Version mismatch ?)
logger.debug("docker plugin - Docker API error (%s)" % e)
# Try the connection with the server version
version = re.search('(?:server API version|server)\:\ (.*)\)\".*\)', str(e))
if version:
logger.debug("docker plugin - Try connection with Docker API version %s" % version.group(1))
ret = self.connect(version=version.group(1))
else:
logger.debug("docker plugin - Can not retreive Docker server version")
ret = None
else:
# API error
logger.error("docker plugin - Docker API error (%s)" % e)
ret = None
except Exception as e:
# Others exceptions...
# Connexion error (Docker not detected)
logger.error("docker plugin - Can't connect to the Docker server (%s)" % e)
ret = None
ret = docker.from_env()
# Log an info if Docker plugin is disabled
if ret is None:
logger.debug("docker plugin - Docker plugin is disable because an error has been detected")
# TODO: manage connection error
return ret
@ -179,22 +109,10 @@ class Plugin(GlancesPlugin):
@GlancesPlugin._log_result_decorator
def update(self):
"""Update Docker stats using the input method."""
global docker_tag
# Reset stats
self.reset()
# Get the current Docker API client
if not self.docker_client:
# First time, try to connect to the server
try:
self.docker_client = self.connect()
except Exception:
docker_tag = False
else:
if self.docker_client is None:
docker_tag = False
# The Docker-py lib is mandatory
if not docker_tag:
return self.stats
@ -219,35 +137,27 @@ class Plugin(GlancesPlugin):
logger.error("{} plugin - Cannot get Docker version ({})".format(self.plugin_name, e))
return self.stats
# Container globals information
# Example: [{u'Status': u'Up 36 seconds',
# u'Created': 1420378904,
# u'Image': u'nginx:1',
# u'Ports': [{u'Type': u'tcp', u'PrivatePort': 443},
# {u'IP': u'0.0.0.0', u'Type': u'tcp', u'PublicPort': 8080, u'PrivatePort': 80}],
# u'Command': u"nginx -g 'daemon off;'",
# u'Names': [u'/webstack_nginx_1'],
# u'Id': u'b0da859e84eb4019cf1d965b15e9323006e510352c402d2f442ea632d61faaa5'}]
# Update current containers list
try:
self.stats['containers'] = self.docker_client.containers() or []
# Issue #1152: Docker module doesn't export details about stopped containers
# It could be done here by setting all=True but the list is too long...
containers = self.docker_client.containers.list(all=False) or []
except Exception as e:
logger.error("{} plugin - Cannot get containers list ({})".format(self.plugin_name, e))
return self.stats
# Start new thread for new container
for container in self.stats['containers']:
if container['Id'] not in self.thread_list:
for container in containers:
if container.id not in self.thread_list:
# Thread did not exist in the internal dict
# Create it and add it to the internal dict
logger.debug("{} plugin - Create thread for container {}".format(self.plugin_name, container['Id'][:12]))
t = ThreadDockerGrabber(self.docker_client, container['Id'])
self.thread_list[container['Id']] = t
logger.debug("{} plugin - Create thread for container {}".format(self.plugin_name, container.id[:12]))
t = ThreadDockerGrabber(container)
self.thread_list[container.id] = t
t.start()
# Stop threads for non-existing containers
nonexisting_containers = set(iterkeys(self.thread_list)) - set([c['Id'] for c in self.stats['containers']])
nonexisting_containers = set(iterkeys(self.thread_list)) - set([c.id for c in containers])
for container_id in nonexisting_containers:
# Stop the thread
logger.debug("{} plugin - Stop thread for old container {}".format(self.plugin_name, container_id[:12]))
@ -256,17 +166,30 @@ class Plugin(GlancesPlugin):
del self.thread_list[container_id]
# Get stats for all containers
for container in self.stats['containers']:
self.stats['containers'] = []
for container in containers:
# Init the stats for the current container
container_stats = {}
# The key is the container name and not the Id
container['key'] = self.get_key()
container_stats['key'] = self.get_key()
# Export name (first name in the list, without the /)
container['name'] = container['Names'][0][1:]
container['cpu'] = self.get_docker_cpu(container['Id'], self.thread_list[container['Id']].stats)
container['memory'] = self.get_docker_memory(container['Id'], self.thread_list[container['Id']].stats)
container['network'] = self.get_docker_network(container['Id'], self.thread_list[container['Id']].stats)
container['io'] = self.get_docker_io(container['Id'], self.thread_list[container['Id']].stats)
container_stats['name'] = container.name
# Global stats (from attrs)
container_stats['Status'] = container.attrs['State']['Status']
container_stats['Command'] = container.attrs['Config']['Entrypoint']
# Standards stats
if container_stats['Status'] in ('running', 'paused'):
container_stats['cpu'] = self.get_docker_cpu(container.id, self.thread_list[container.id].stats)
container_stats['memory'] = self.get_docker_memory(container.id, self.thread_list[container.id].stats)
container_stats['network'] = self.get_docker_network(container.id, self.thread_list[container.id].stats)
container_stats['io'] = self.get_docker_io(container.id, self.thread_list[container.id].stats)
else:
container_stats['cpu'] = {}
container_stats['memory'] = {}
container_stats['network'] = {}
container_stats['io'] = {}
# Add current container stats to the stats list
self.stats['containers'].append(container_stats)
elif self.input_method == 'snmp':
# Update stats using SNMP
@ -454,7 +377,7 @@ class Plugin(GlancesPlugin):
iow = [i for i in iocounters['io_service_bytes_recursive'] if i['op'] == 'Write'][0]['value']
ior_old = [i for i in self.iocounters_old[container_id]['io_service_bytes_recursive'] if i['op'] == 'Read'][0]['value']
iow_old = [i for i in self.iocounters_old[container_id]['io_service_bytes_recursive'] if i['op'] == 'Write'][0]['value']
except (IndexError, KeyError) as e:
except (IndexError, KeyError, TypeError) as e:
# all_stats do not have io information
logger.debug("docker plugin - Cannot grab block IO usage for container {} ({})".format(container_id, e))
else:
@ -634,10 +557,14 @@ class Plugin(GlancesPlugin):
def container_alert(self, status):
"""Analyse the container status."""
if "Paused" in status:
return 'CAREFUL'
else:
if status in ('running'):
return 'OK'
elif status in ('exited'):
return 'WARNING'
elif status in ('dead'):
return 'CRITICAL'
else:
return 'CAREFUL'
class ThreadDockerGrabber(threading.Thread):
@ -647,19 +574,19 @@ class ThreadDockerGrabber(threading.Thread):
stats is a dict
"""
def __init__(self, docker_client, container_id):
def __init__(self, container):
"""Init the class:
docker_client: instance of Docker-py client
container_id: Id of the container"""
logger.debug("docker plugin - Create thread for container {}".format(container_id[:12]))
container: instance of Docker-py Container
"""
super(ThreadDockerGrabber, self).__init__()
# Event needed to stop properly the thread
self._stopper = threading.Event()
# The docker-py return stats as a stream
self._container_id = container_id
self._stats_stream = docker_client.stats(container_id, decode=True)
self._container = container
self._stats_stream = container.stats(decode=True)
# The class return the stats as a dict
self._stats = {}
logger.debug("docker plugin - Create thread for container {}".format(self._container.name))
def run(self):
"""Function called to grab stats.
@ -683,7 +610,7 @@ class ThreadDockerGrabber(threading.Thread):
def stop(self, timeout=None):
"""Stop the thread"""
logger.debug("docker plugin - Close thread for container {}".format(self._container_id[:12]))
logger.debug("docker plugin - Close thread for container {}".format(self._container.name))
self._stopper.set()
def stopped(self):