# -*- coding: utf-8 -*-
|
|
#
|
|
# This file is part of Glances.
|
|
#
|
|
# Copyright (C) 2018 Nicolargo <nicolas@nicolargo.com>
|
|
#
|
|
# Glances is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Glances is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""Manage the Glances client."""
|
|
|
|
import json
|
|
import socket
|
|
import sys
|
|
|
|
from glances import __version__
|
|
from glances.compat import Fault, ProtocolError, ServerProxy, Transport
|
|
from glances.logger import logger
|
|
from glances.stats_client import GlancesStatsClient
|
|
from glances.outputs.glances_curses import GlancesCursesClient
|
|
|
|
|
|
class GlancesClientTransport(Transport):
|
|
|
|
"""This class overwrite the default XML-RPC transport and manage timeout."""
|
|
|
|
def set_timeout(self, timeout):
|
|
self.timeout = timeout
|
|
|
|
|
|
class GlancesClient(object):
|
|
|
|
"""This class creates and manages the TCP client."""
|
|
|
|
def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
|
|
# Store the arg/config
|
|
self.args = args
|
|
self.config = config
|
|
# Quiet mode
|
|
self._quiet = args.quiet
|
|
|
|
# Default client mode
|
|
self._client_mode = 'glances'
|
|
|
|
# Return to browser or exit
|
|
self.return_to_browser = return_to_browser
|
|
|
|
# Build the URI
|
|
if args.password != "":
|
|
self.uri = 'http://{}:{}@{}:{}'.format(args.username, args.password,
|
|
args.client, args.port)
|
|
else:
|
|
self.uri = 'http://{}:{}'.format(args.client, args.port)
|
|
logger.debug("Try to connect to {}".format(self.uri))
|
|
|
|
# Try to connect to the URI
|
|
transport = GlancesClientTransport()
|
|
# Configure the server timeout
|
|
transport.set_timeout(timeout)
|
|
try:
|
|
self.client = ServerProxy(self.uri, transport=transport)
|
|
except Exception as e:
|
|
self.log_and_exit("Client couldn't create socket {}: {}".format(self.uri, e))
|
|
|
|
@property
|
|
def quiet(self):
|
|
return self._quiet
|
|
|
|
def log_and_exit(self, msg=''):
|
|
"""Log and exit."""
|
|
if not self.return_to_browser:
|
|
logger.critical(msg)
|
|
sys.exit(2)
|
|
else:
|
|
logger.error(msg)
|
|
|
|
@property
|
|
def client_mode(self):
|
|
"""Get the client mode."""
|
|
return self._client_mode
|
|
|
|
@client_mode.setter
|
|
def client_mode(self, mode):
|
|
"""Set the client mode.
|
|
|
|
- 'glances' = Glances server (default)
|
|
- 'snmp' = SNMP (fallback)
|
|
"""
|
|
self._client_mode = mode
|
|
|
|
def _login_glances(self):
|
|
"""Login to a Glances server"""
|
|
client_version = None
|
|
try:
|
|
client_version = self.client.init()
|
|
except socket.error as err:
|
|
# Fallback to SNMP
|
|
self.client_mode = 'snmp'
|
|
logger.error("Connection to Glances server failed ({} {})".format(err.errno, err.strerror))
|
|
fallbackmsg = 'No Glances server found on {}. Trying fallback to SNMP...'.format(self.uri)
|
|
if not self.return_to_browser:
|
|
print(fallbackmsg)
|
|
else:
|
|
logger.info(fallbackmsg)
|
|
except ProtocolError as err:
|
|
# Other errors
|
|
msg = "Connection to server {} failed".format(self.uri)
|
|
if err.errcode == 401:
|
|
msg += " (Bad username/password)"
|
|
else:
|
|
msg += " ({} {})".format(err.errcode, err.errmsg)
|
|
self.log_and_exit(msg)
|
|
return False
|
|
|
|
if self.client_mode == 'glances':
|
|
# Check that both client and server are in the same major version
|
|
if __version__.split('.')[0] == client_version.split('.')[0]:
|
|
# Init stats
|
|
self.stats = GlancesStatsClient(config=self.config, args=self.args)
|
|
self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
|
|
logger.debug("Client version: {} / Server version: {}".format(__version__, client_version))
|
|
else:
|
|
self.log_and_exit(('Client and server not compatible: '
|
|
'Client version: {} / Server version: {}'.format(__version__, client_version)))
|
|
return False
|
|
|
|
return True
|
|
|
|
def _login_snmp(self):
|
|
"""Login to a SNMP server"""
|
|
logger.info("Trying to grab stats by SNMP...")
|
|
|
|
from glances.stats_client_snmp import GlancesStatsClientSNMP
|
|
|
|
# Init stats
|
|
self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args)
|
|
|
|
if not self.stats.check_snmp():
|
|
self.log_and_exit("Connection to SNMP server failed")
|
|
return False
|
|
|
|
return True
|
|
|
|
def login(self):
|
|
"""Logon to the server."""
|
|
|
|
if self.args.snmp_force:
|
|
# Force SNMP instead of Glances server
|
|
self.client_mode = 'snmp'
|
|
else:
|
|
# First of all, trying to connect to a Glances server
|
|
if not self._login_glances():
|
|
return False
|
|
|
|
# Try SNMP mode
|
|
if self.client_mode == 'snmp':
|
|
if not self._login_snmp():
|
|
return False
|
|
|
|
# Load limits from the configuration file
|
|
# Each client can choose its owns limits
|
|
logger.debug("Load limits from the client configuration file")
|
|
self.stats.load_limits(self.config)
|
|
|
|
# Init screen
|
|
if self.quiet:
|
|
# In quiet mode, nothing is displayed
|
|
logger.info("Quiet mode is ON: Nothing will be displayed")
|
|
else:
|
|
self.screen = GlancesCursesClient(config=self.config, args=self.args)
|
|
|
|
# Return True: OK
|
|
return True
|
|
|
|
def update(self):
|
|
"""Update stats from Glances/SNMP server."""
|
|
if self.client_mode == 'glances':
|
|
return self.update_glances()
|
|
elif self.client_mode == 'snmp':
|
|
return self.update_snmp()
|
|
else:
|
|
self.end()
|
|
logger.critical("Unknown server mode: {}".format(self.client_mode))
|
|
sys.exit(2)
|
|
|
|
def update_glances(self):
|
|
"""Get stats from Glances server.
|
|
|
|
Return the client/server connection status:
|
|
- Connected: Connection OK
|
|
- Disconnected: Connection NOK
|
|
"""
|
|
# Update the stats
|
|
try:
|
|
server_stats = json.loads(self.client.getAll())
|
|
except socket.error:
|
|
# Client cannot get server stats
|
|
return "Disconnected"
|
|
except Fault:
|
|
# Client cannot get server stats (issue #375)
|
|
return "Disconnected"
|
|
else:
|
|
# Put it in the internal dict
|
|
self.stats.update(server_stats)
|
|
return "Connected"
|
|
|
|
def update_snmp(self):
|
|
"""Get stats from SNMP server.
|
|
|
|
Return the client/server connection status:
|
|
- SNMP: Connection with SNMP server OK
|
|
- Disconnected: Connection NOK
|
|
"""
|
|
# Update the stats
|
|
try:
|
|
self.stats.update()
|
|
except Exception:
|
|
# Client cannot get SNMP server stats
|
|
return "Disconnected"
|
|
else:
|
|
# Grab success
|
|
return "SNMP"
|
|
|
|
def serve_forever(self):
|
|
"""Main client loop."""
|
|
|
|
# Test if client and server are in the same major version
|
|
if not self.login():
|
|
logger.critical("The server version is not compatible with the client")
|
|
self.end()
|
|
return self.client_mode
|
|
|
|
exitkey = False
|
|
try:
|
|
while True and not exitkey:
|
|
# Update the stats
|
|
cs_status = self.update()
|
|
|
|
# Update the screen
|
|
if not self.quiet:
|
|
exitkey = self.screen.update(self.stats,
|
|
cs_status=cs_status,
|
|
return_to_browser=self.return_to_browser)
|
|
|
|
# Export stats using export modules
|
|
self.stats.export(self.stats)
|
|
except Exception as e:
|
|
logger.critical(e)
|
|
self.end()
|
|
|
|
return self.client_mode
|
|
|
|
def end(self):
|
|
"""End of the client session."""
|
|
if not self.quiet:
|
|
self.screen.end()
|