mirror of https://github.com/nicolargo/glances.git
Refactor server list code in order to put all the methodin the servers_list.py file
This commit is contained in:
parent
799dcab034
commit
e1dd979b86
|
|
@ -8,31 +8,13 @@
|
|||
|
||||
"""Manage the Glances client browser (list of Glances server)."""
|
||||
|
||||
import threading
|
||||
import webbrowser
|
||||
|
||||
from defusedxml import xmlrpc
|
||||
|
||||
from glances import __apiversion__
|
||||
from glances.client import GlancesClient, GlancesClientTransport
|
||||
from glances.globals import json_loads
|
||||
from glances.client import GlancesClient
|
||||
from glances.logger import LOG_FILENAME, logger
|
||||
from glances.outputs.glances_curses_browser import GlancesCursesBrowser
|
||||
from glances.password_list import GlancesPasswordList as GlancesPassword
|
||||
from glances.servers_list import GlancesServersList
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError as e:
|
||||
import_requests_error_tag = True
|
||||
# Display debug message if import error
|
||||
logger.warning(f"Missing Python Lib ({e}), Client browser will not grab stats from Glances REST server")
|
||||
else:
|
||||
import_requests_error_tag = False
|
||||
|
||||
# Correct issue #1025 by monkey path the xmlrpc lib
|
||||
xmlrpc.monkey_patch()
|
||||
|
||||
|
||||
class GlancesClientBrowser:
|
||||
"""This class creates and manages the TCP client browser (servers list)."""
|
||||
|
|
@ -42,127 +24,12 @@ class GlancesClientBrowser:
|
|||
self.args = args
|
||||
self.config = config
|
||||
|
||||
# Load the configuration file
|
||||
self.password = None
|
||||
self.load()
|
||||
|
||||
# Init the server list
|
||||
self.servers_list = GlancesServersList(config=config, args=args)
|
||||
|
||||
# Init screen
|
||||
self.screen = GlancesCursesBrowser(args=self.args)
|
||||
|
||||
def load(self):
|
||||
# Init the password list (if defined)
|
||||
self.password = GlancesPassword(config=self.config)
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (list of dict).
|
||||
|
||||
Merge of static + autodiscover servers list.
|
||||
"""
|
||||
return self.servers_list.get_servers_list()
|
||||
|
||||
def __get_uri(self, server):
|
||||
"""Return the URI for the given server dict."""
|
||||
# Select the connection mode (with or without password)
|
||||
if server['password'] != "":
|
||||
if server['status'] == 'PROTECTED':
|
||||
# Try with the preconfigure password (only if status is PROTECTED)
|
||||
clear_password = self.password.get_password(server['name'])
|
||||
if clear_password is not None:
|
||||
server['password'] = self.password.get_hash(clear_password)
|
||||
uri = 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port'])
|
||||
else:
|
||||
uri = 'http://{}:{}'.format(server['ip'], server['port'])
|
||||
return uri
|
||||
|
||||
def __get_key(self, column):
|
||||
server_key = column.get('plugin') + '_' + column.get('field')
|
||||
if 'key' in column:
|
||||
server_key += '_' + column.get('key')
|
||||
return server_key
|
||||
|
||||
def __update_stats_rpc(self, uri, server):
|
||||
# Try to connect to the server
|
||||
t = GlancesClientTransport()
|
||||
t.set_timeout(3)
|
||||
|
||||
# Get common stats from Glances server
|
||||
try:
|
||||
s = xmlrpc.xmlrpc_client.ServerProxy(uri, transport=t)
|
||||
except Exception as e:
|
||||
logger.warning(f"Client browser couldn't create socket ({e})")
|
||||
return server
|
||||
|
||||
# Get the stats
|
||||
for column in self.servers_list.get_columns():
|
||||
server_key = self.__get_key(column)
|
||||
try:
|
||||
# Value
|
||||
v_json = json_loads(s.getPlugin(column['plugin']))
|
||||
if 'key' in column:
|
||||
v_json = [i for i in v_json if i[i['key']].lower() == column['key'].lower()][0]
|
||||
server[server_key] = v_json[column['field']]
|
||||
# Decoration
|
||||
d_json = json_loads(s.getPluginView(column['plugin']))
|
||||
if 'key' in column:
|
||||
d_json = d_json.get(column['key'])
|
||||
server[server_key + '_decoration'] = d_json[column['field']]['decoration']
|
||||
except (KeyError, IndexError, xmlrpc.xmlrpc_client.Fault) as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
except OSError as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
server['status'] = 'OFFLINE'
|
||||
except xmlrpc.xmlrpc_client.ProtocolError as e:
|
||||
if e.errcode == 401:
|
||||
# Error 401 (Authentication failed)
|
||||
# Password is not the good one...
|
||||
server['password'] = None
|
||||
server['status'] = 'PROTECTED'
|
||||
else:
|
||||
server['status'] = 'OFFLINE'
|
||||
logger.debug(f"Cannot grab stats from server ({e.errcode} {e.errmsg})")
|
||||
else:
|
||||
# Status
|
||||
server['status'] = 'ONLINE'
|
||||
|
||||
return server
|
||||
|
||||
def __update_stats_rest(self, uri, server):
|
||||
try:
|
||||
requests.get(f'{uri}/status', timeout=3)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
server['status'] = 'OFFLINE'
|
||||
return server
|
||||
else:
|
||||
server['status'] = 'ONLINE'
|
||||
|
||||
for column in self.servers_list.get_columns():
|
||||
server_key = self.__get_key(column)
|
||||
try:
|
||||
r = requests.get(f'{uri}/{column['plugin']}/{column['field']}', timeout=3)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
return server
|
||||
else:
|
||||
server[server_key] = r.json()[column['field']]
|
||||
|
||||
return server
|
||||
|
||||
def __update_stats(self, server):
|
||||
"""Update stats for the given server (picked from the server list)"""
|
||||
# Get the server URI
|
||||
uri = self.__get_uri(server)
|
||||
|
||||
if server['protocol'].lower() == 'rpc':
|
||||
self.__update_stats_rpc(uri, server)
|
||||
elif server['protocol'].lower() == 'rest' and not import_requests_error_tag:
|
||||
self.__update_stats_rest(f'{uri}/api/{__apiversion__}', server)
|
||||
|
||||
return server
|
||||
|
||||
def __display_server(self, server):
|
||||
"""Connect and display the given server"""
|
||||
# Display the Glances client for the selected server
|
||||
|
|
@ -174,7 +41,7 @@ class GlancesClientBrowser:
|
|||
'Open the WebUI {}:{} in a Web Browser'.format(server['name'], server['port']), duration=1
|
||||
)
|
||||
# Try to open a Webbrowser
|
||||
webbrowser.open(self.__get_uri(server), new=2, autoraise=1)
|
||||
webbrowser.open(self.servers_list.get_uri(server), new=2, autoraise=1)
|
||||
self.screen.active_server = None
|
||||
return
|
||||
|
||||
|
|
@ -185,8 +52,11 @@ class GlancesClientBrowser:
|
|||
# A password is needed to access to the server's stats
|
||||
if server['password'] is None:
|
||||
# First of all, check if a password is available in the [passwords] section
|
||||
clear_password = self.password.get_password(server['name'])
|
||||
if clear_password is None or self.get_servers_list()[self.screen.active_server]['status'] == 'PROTECTED':
|
||||
clear_password = self.servers_list.password.get_password(server['name'])
|
||||
if (
|
||||
clear_password is None
|
||||
or self.servers_list.get_servers_list()[self.screen.active_server]['status'] == 'PROTECTED'
|
||||
):
|
||||
# Else, the password should be enter by the user
|
||||
# Display a popup to enter password
|
||||
clear_password = self.screen.display_popup(
|
||||
|
|
@ -194,7 +64,7 @@ class GlancesClientBrowser:
|
|||
)
|
||||
# Store the password for the selected server
|
||||
if clear_password is not None:
|
||||
self.set_in_selected('password', self.password.get_hash(clear_password))
|
||||
self.set_in_selected('password', self.servers_list.password.get_hash(clear_password))
|
||||
|
||||
# Display the Glance client on the selected server
|
||||
logger.info("Connect Glances client to the {} server".format(server['key']))
|
||||
|
|
@ -240,31 +110,16 @@ class GlancesClientBrowser:
|
|||
def __serve_forever(self):
|
||||
"""Main client loop."""
|
||||
# No need to update the server list
|
||||
# It's done by the GlancesAutoDiscoverListener class (autodiscover.py)
|
||||
# Or define statically in the configuration file (module static_list.py)
|
||||
# For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
|
||||
thread_list = {}
|
||||
while not self.screen.is_end:
|
||||
logger.debug(f"Iter through the following server list: {self.get_servers_list()}")
|
||||
for v in self.get_servers_list():
|
||||
key = v["key"]
|
||||
thread = thread_list.get(key, None)
|
||||
if thread is None or thread.is_alive() is False:
|
||||
thread = threading.Thread(target=self.__update_stats, args=[v])
|
||||
thread_list[key] = thread
|
||||
thread.start()
|
||||
# Update the stats in the servers list
|
||||
self.servers_list.update_servers_stats()
|
||||
|
||||
# Update the screen (list or Glances client)
|
||||
if self.screen.active_server is None:
|
||||
# Display the Glances browser
|
||||
self.screen.update(self.get_servers_list())
|
||||
# Display Glances browser (servers list)
|
||||
self.screen.update(self.servers_list.get_servers_list())
|
||||
else:
|
||||
# Display the active server
|
||||
self.__display_server(self.get_servers_list()[self.screen.active_server])
|
||||
|
||||
# exit key pressed
|
||||
for thread in thread_list.values():
|
||||
thread.join()
|
||||
# Display selected Glances server
|
||||
self.__display_server(self.servers_list.get_servers_list()[self.screen.active_server])
|
||||
|
||||
def serve_forever(self):
|
||||
"""Wrapper to the serve_forever function.
|
||||
|
|
|
|||
|
|
@ -170,7 +170,13 @@ class GlancesRestfulApi:
|
|||
self.url_prefix = self.url_prefix.rstrip('/')
|
||||
logger.debug(f'URL prefix: {self.url_prefix}')
|
||||
|
||||
def __update__(self):
|
||||
def __update_stats(self):
|
||||
# Never update more than 1 time per cached_time
|
||||
if self.timer.finished():
|
||||
self.stats.update()
|
||||
self.timer = Timer(self.args.cached_time)
|
||||
|
||||
def __update_servers_list(self):
|
||||
# Never update more than 1 time per cached_time
|
||||
if self.timer.finished():
|
||||
self.stats.update()
|
||||
|
|
@ -210,6 +216,7 @@ class GlancesRestfulApi:
|
|||
f'{base_path}/all/limits': self._api_all_limits,
|
||||
f'{base_path}/all/views': self._api_all_views,
|
||||
f'{base_path}/pluginslist': self._api_plugins,
|
||||
f'{base_path}/serverslist': self._api_servers_list,
|
||||
f'{plugin_path}': self._api,
|
||||
f'{plugin_path}/history': self._api_history,
|
||||
f'{plugin_path}/history/{{nb}}': self._api_history,
|
||||
|
|
@ -310,7 +317,7 @@ class GlancesRestfulApi:
|
|||
refresh_time = request.query_params.get('refresh', default=max(1, int(self.args.time)))
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
# Display
|
||||
return self._templates.TemplateResponse("index.html", {"request": request, "refresh_time": refresh_time})
|
||||
|
|
@ -375,7 +382,7 @@ class GlancesRestfulApi:
|
|||
HTTP/1.1 404 Not Found
|
||||
"""
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
plist = self.plugins_list
|
||||
|
|
@ -384,6 +391,14 @@ class GlancesRestfulApi:
|
|||
|
||||
return GlancesJSONResponse(plist)
|
||||
|
||||
def _api_servers_list(self):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Return the JSON representation of the servers list (for browser mode)
|
||||
HTTP/200 if OK
|
||||
"""
|
||||
self.__update_servers_list()
|
||||
|
||||
def _api_all(self):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
|
|
@ -401,7 +416,7 @@ class GlancesRestfulApi:
|
|||
logger.debug(f"Debug file ({fname}) not found")
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat ID
|
||||
|
|
@ -454,7 +469,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat ID
|
||||
|
|
@ -485,7 +500,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat ID
|
||||
|
|
@ -512,7 +527,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat ID
|
||||
|
|
@ -572,7 +587,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat views
|
||||
|
|
@ -597,7 +612,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value of the stat history
|
||||
|
|
@ -656,7 +671,7 @@ class GlancesRestfulApi:
|
|||
self._check_if_plugin_available(plugin)
|
||||
|
||||
# Update the stat
|
||||
self.__update__()
|
||||
self.__update_stats()
|
||||
|
||||
try:
|
||||
# Get the RAW value
|
||||
|
|
|
|||
|
|
@ -8,9 +8,30 @@
|
|||
|
||||
"""Manage the servers list used in TUI and WEBUI Central Browser mode"""
|
||||
|
||||
import threading
|
||||
|
||||
from defusedxml import xmlrpc
|
||||
|
||||
from glances import __apiversion__
|
||||
from glances.client import GlancesClientTransport
|
||||
from glances.globals import json_loads
|
||||
from glances.logger import logger
|
||||
from glances.password_list import GlancesPasswordList as GlancesPassword
|
||||
from glances.servers_list_dynamic import GlancesAutoDiscoverServer
|
||||
from glances.servers_list_static import GlancesStaticServer
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError as e:
|
||||
import_requests_error_tag = True
|
||||
# Display debug message if import error
|
||||
logger.warning(f"Missing Python Lib ({e}), Client browser will not grab stats from Glances REST server")
|
||||
else:
|
||||
import_requests_error_tag = False
|
||||
|
||||
# Correct issue #1025 by monkey path the xmlrpc lib
|
||||
xmlrpc.monkey_patch()
|
||||
|
||||
|
||||
class GlancesServersList:
|
||||
def __init__(self, config=None, args=None):
|
||||
|
|
@ -18,8 +39,9 @@ class GlancesServersList:
|
|||
self.args = args
|
||||
self.config = config
|
||||
|
||||
# Init the servers list defined in the Glances configuration file
|
||||
# Init the servers and passwords list defined in the Glances configuration file
|
||||
self.static_server = None
|
||||
self.password = None
|
||||
self.load()
|
||||
|
||||
# Init the dynamic servers list by starting a Zeroconf listener
|
||||
|
|
@ -27,10 +49,16 @@ class GlancesServersList:
|
|||
if not self.args.disable_autodiscover:
|
||||
self.autodiscover_server = GlancesAutoDiscoverServer()
|
||||
|
||||
# Stats are updated in thread
|
||||
# Create a dict of threads
|
||||
self.threads_list = {}
|
||||
|
||||
def load(self):
|
||||
"""Load server and password list from the configuration file."""
|
||||
# Init the static server list
|
||||
self.static_server = GlancesStaticServer(config=self.config)
|
||||
# Init the password list (if defined)
|
||||
self.password = GlancesPassword(config=self.config)
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (list of dict).
|
||||
|
|
@ -46,9 +74,33 @@ class GlancesServersList:
|
|||
|
||||
return ret
|
||||
|
||||
def update_servers_stats(self):
|
||||
"""For each server in the servers list, update the stats"""
|
||||
for v in self.get_servers_list():
|
||||
key = v["key"]
|
||||
thread = self.threads_list.get(key, None)
|
||||
if thread is None or thread.is_alive() is False:
|
||||
thread = threading.Thread(target=self.__update_stats, args=[v])
|
||||
self.threads_list[key] = thread
|
||||
thread.start()
|
||||
|
||||
def get_columns(self):
|
||||
return self.static_server.get_columns()
|
||||
|
||||
def get_uri(self, server):
|
||||
"""Return the URI for the given server dict."""
|
||||
# Select the connection mode (with or without password)
|
||||
if server['password'] != "":
|
||||
if server['status'] == 'PROTECTED':
|
||||
# Try with the preconfigure password (only if status is PROTECTED)
|
||||
clear_password = self.password.get_password(server['name'])
|
||||
if clear_password is not None:
|
||||
server['password'] = self.password.get_hash(clear_password)
|
||||
uri = 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port'])
|
||||
else:
|
||||
uri = 'http://{}:{}'.format(server['ip'], server['port'])
|
||||
return uri
|
||||
|
||||
def set_in_selected(self, selected, key, value):
|
||||
"""Set the (key, value) for the selected server in the list."""
|
||||
# Static list then dynamic one
|
||||
|
|
@ -56,3 +108,89 @@ class GlancesServersList:
|
|||
self.autodiscover_server.set_server(selected - len(self.static_server.get_servers_list()), key, value)
|
||||
else:
|
||||
self.static_server.set_server(selected, key, value)
|
||||
|
||||
def __update_stats(self, server):
|
||||
"""Update stats for the given server (picked from the server list)"""
|
||||
# Get the server URI
|
||||
uri = self.get_uri(server)
|
||||
|
||||
if server['protocol'].lower() == 'rpc':
|
||||
self.__update_stats_rpc(uri, server)
|
||||
elif server['protocol'].lower() == 'rest' and not import_requests_error_tag:
|
||||
self.__update_stats_rest(f'{uri}/api/{__apiversion__}', server)
|
||||
|
||||
return server
|
||||
|
||||
def __update_stats_rpc(self, uri, server):
|
||||
# Try to connect to the server
|
||||
t = GlancesClientTransport()
|
||||
t.set_timeout(3)
|
||||
|
||||
# Get common stats from Glances server
|
||||
try:
|
||||
s = xmlrpc.xmlrpc_client.ServerProxy(uri, transport=t)
|
||||
except Exception as e:
|
||||
logger.warning(f"Client browser couldn't create socket ({e})")
|
||||
return server
|
||||
|
||||
# Get the stats
|
||||
for column in self.static_server.get_columns():
|
||||
server_key = self.__get_key(column)
|
||||
try:
|
||||
# Value
|
||||
v_json = json_loads(s.getPlugin(column['plugin']))
|
||||
if 'key' in column:
|
||||
v_json = [i for i in v_json if i[i['key']].lower() == column['key'].lower()][0]
|
||||
server[server_key] = v_json[column['field']]
|
||||
# Decoration
|
||||
d_json = json_loads(s.getPluginView(column['plugin']))
|
||||
if 'key' in column:
|
||||
d_json = d_json.get(column['key'])
|
||||
server[server_key + '_decoration'] = d_json[column['field']]['decoration']
|
||||
except (KeyError, IndexError, xmlrpc.xmlrpc_client.Fault) as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
except OSError as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
server['status'] = 'OFFLINE'
|
||||
except xmlrpc.xmlrpc_client.ProtocolError as e:
|
||||
if e.errcode == 401:
|
||||
# Error 401 (Authentication failed)
|
||||
# Password is not the good one...
|
||||
server['password'] = None
|
||||
server['status'] = 'PROTECTED'
|
||||
else:
|
||||
server['status'] = 'OFFLINE'
|
||||
logger.debug(f"Cannot grab stats from server ({e.errcode} {e.errmsg})")
|
||||
else:
|
||||
# Status
|
||||
server['status'] = 'ONLINE'
|
||||
|
||||
return server
|
||||
|
||||
def __update_stats_rest(self, uri, server):
|
||||
try:
|
||||
requests.get(f'{uri}/status', timeout=3)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
server['status'] = 'OFFLINE'
|
||||
return server
|
||||
else:
|
||||
server['status'] = 'ONLINE'
|
||||
|
||||
for column in self.static_server.get_columns():
|
||||
server_key = self.__get_key(column)
|
||||
try:
|
||||
r = requests.get(f'{uri}/{column['plugin']}/{column['field']}', timeout=3)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug(f"Error while grabbing stats form server ({e})")
|
||||
return server
|
||||
else:
|
||||
server[server_key] = r.json()[column['field']]
|
||||
|
||||
return server
|
||||
|
||||
def __get_key(self, column):
|
||||
server_key = column.get('plugin') + '_' + column.get('field')
|
||||
if 'key' in column:
|
||||
server_key += '_' + column.get('key')
|
||||
return server_key
|
||||
|
|
|
|||
Loading…
Reference in New Issue