mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-24 13:23:12 +03:00
Support for Docker API < 2.0 is deprecated (docker plugins)
This commit is contained in:
parent
98716b551b
commit
eb3cd8fd20
4
NEWS
4
NEWS
@ -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
|
||||
==============================================================================
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user