diff options
Diffstat (limited to 'glances/client_browser.py')
-rw-r--r-- | glances/client_browser.py | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/glances/client_browser.py b/glances/client_browser.py new file mode 100644 index 00000000..cfcf01a2 --- /dev/null +++ b/glances/client_browser.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2015 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 browser (list of Glances server).""" + +import json +import socket + +from glances.compat import Fault, ProtocolError, ServerProxy +from glances.autodiscover import GlancesAutoDiscoverServer +from glances.client import GlancesClient, GlancesClientTransport +from glances.logger import logger +from glances.password_list import GlancesPasswordList as GlancesPassword +from glances.static_list import GlancesStaticServer +from glances.outputs.glances_curses import GlancesCursesBrowser + + +class GlancesClientBrowser(object): + + """This class creates and manages the TCP client browser (servers list).""" + + def __init__(self, config=None, args=None): + # Store the arg/config + self.args = args + self.config = config + self.static_server = None + self.password = None + + # Load the configuration file + self.load() + + # Start the autodiscover mode (Zeroconf listener) + if not self.args.disable_autodiscover: + self.autodiscover_server = GlancesAutoDiscoverServer() + else: + self.autodiscover_server = None + + # Init screen + self.screen = GlancesCursesBrowser(args=self.args) + + def load(self): + """Load server and password list from the confiuration file.""" + # Init the static server list (if defined) + 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). + + Merge of static + autodiscover servers list. + """ + ret = [] + + if self.args.browser: + ret = self.static_server.get_servers_list() + if self.autodiscover_server is not None: + ret = self.static_server.get_servers_list() + self.autodiscover_server.get_servers_list() + + return ret + + 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.sha256_hash(clear_password) + return 'http://{0}:{1}@{2}:{3}'.format(server['username'], server['password'], + server['ip'], server['port']) + else: + return 'http://{0}:{1}'.format(server['ip'], server['port']) + + def __serve_forever(self): + """Main client loop.""" + while True: + # No need to update the server list + # It's done by the GlancesAutoDiscoverListener class (autodiscover.py) + # Or define staticaly in the configuration file (module static_list.py) + # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...) + # logger.debug(self.get_servers_list()) + try: + for v in self.get_servers_list(): + # Do not retreive stats for statics server + # Why ? Because for each offline servers, the timeout will be reached + # So ? The curse interface freezes + if v['type'] == 'STATIC' and v['status'] in ['UNKNOWN', 'SNMP', 'OFFLINE']: + continue + + # Get the server URI + uri = self.__get_uri(v) + + # Try to connect to the server + t = GlancesClientTransport() + t.set_timeout(3) + + # Get common stats + try: + s = ServerProxy(uri, transport=t) + except Exception as e: + logger.warning( + "Client browser couldn't create socket {0}: {1}".format(uri, e)) + else: + # Mandatory stats + try: + # CPU% + cpu_percent = 100 - json.loads(s.getCpu())['idle'] + v['cpu_percent'] = '{0:.1f}'.format(cpu_percent) + # MEM% + v['mem_percent'] = json.loads(s.getMem())['percent'] + # OS (Human Readable name) + v['hr_name'] = json.loads(s.getSystem())['hr_name'] + except (socket.error, Fault, KeyError) as e: + logger.debug( + "Error while grabbing stats form {0}: {1}".format(uri, e)) + v['status'] = 'OFFLINE' + except ProtocolError as e: + if e.errcode == 401: + # Error 401 (Authentication failed) + # Password is not the good one... + v['password'] = None + v['status'] = 'PROTECTED' + else: + v['status'] = 'OFFLINE' + logger.debug("Cannot grab stats from {0} ({1} {2})".format(uri, e.errcode, e.errmsg)) + else: + # Status + v['status'] = 'ONLINE' + + # Optional stats (load is not available on Windows OS) + try: + # LOAD + load_min5 = json.loads(s.getLoad())['min5'] + v['load_min5'] = '{0:.2f}'.format(load_min5) + except Exception as e: + logger.warning( + "Error while grabbing stats form {0}: {1}".format(uri, e)) + # List can change size during iteration... + except RuntimeError: + logger.debug( + "Server list dictionnary change inside the loop (wait next update)") + + # Update the screen (list or Glances client) + if not self.screen.active_server: + # Display the Glances browser + self.screen.update(self.get_servers_list()) + else: + # Display the Glances client for the selected server + logger.debug("Selected server: {0}".format(self.get_servers_list()[self.screen.active_server])) + + # Connection can take time + # Display a popup + self.screen.display_popup( + 'Connect to {0}:{1}'.format(v['name'], v['port']), duration=1) + + # A password is needed to access to the server's stats + if self.get_servers_list()[self.screen.active_server]['password'] is None: + # First of all, check if a password is available in the [passwords] section + clear_password = self.password.get_password(v['name']) + if (clear_password is None or self.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( + 'Password needed for {0}: '.format(v['name']), is_input=True) + # Store the password for the selected server + if clear_password is not None: + self.set_in_selected('password', self.password.sha256_hash(clear_password)) + + # Display the Glance client on the selected server + logger.info("Connect Glances client to the {0} server".format( + self.get_servers_list()[self.screen.active_server]['key'])) + + # Init the client + args_server = self.args + + # Overwrite connection setting + args_server.client = self.get_servers_list()[self.screen.active_server]['ip'] + args_server.port = self.get_servers_list()[self.screen.active_server]['port'] + args_server.username = self.get_servers_list()[self.screen.active_server]['username'] + args_server.password = self.get_servers_list()[self.screen.active_server]['password'] + client = GlancesClient(config=self.config, args=args_server, return_to_browser=True) + + # Test if client and server are in the same major version + if not client.login(): + self.screen.display_popup( + "Sorry, cannot connect to '{0}'\n" + "See 'glances.log' for more details".format(v['name'])) + + # Set the ONLINE status for the selected server + self.set_in_selected('status', 'OFFLINE') + else: + # Start the client loop + # Return connection type: 'glances' or 'snmp' + connection_type = client.serve_forever() + + try: + logger.debug("Disconnect Glances client from the {0} server".format( + self.get_servers_list()[self.screen.active_server]['key'])) + except IndexError: + # Server did not exist anymore + pass + else: + # Set the ONLINE status for the selected server + if connection_type == 'snmp': + self.set_in_selected('status', 'SNMP') + else: + self.set_in_selected('status', 'ONLINE') + + # Return to the browser (no server selected) + self.screen.active_server = None + + def serve_forever(self): + """Wrapper to the serve_forever function. + + This function will restore the terminal to a sane state + before re-raising the exception and generating a traceback. + """ + try: + return self.__serve_forever() + finally: + self.end() + + def set_in_selected(self, key, value): + """Set the (key, value) for the selected server in the list.""" + # Static list then dynamic one + if self.screen.active_server >= len(self.static_server.get_servers_list()): + self.autodiscover_server.set_server( + self.screen.active_server - len(self.static_server.get_servers_list()), + key, value) + else: + self.static_server.set_server(self.screen.active_server, key, value) + + def end(self): + """End of the client browser session.""" + self.screen.end() |