PageRenderTime 40ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/glances/core/glances_client_browser.py

https://gitlab.com/132nd-etcher/glances
Python | 262 lines | 178 code | 26 blank | 58 comment | 22 complexity | d0bb94f40ac5b21d360193748acae5a5 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of Glances.
  4. #
  5. # Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
  6. #
  7. # Glances is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU Lesser General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Glances is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. """Manage the Glances client browser (list of Glances server)."""
  20. # Import system libs
  21. import json
  22. import socket
  23. try:
  24. from xmlrpc.client import ServerProxy, Fault, ProtocolError
  25. except ImportError:
  26. # Python 2
  27. from xmlrpclib import ServerProxy, Fault, ProtocolError
  28. # Import Glances libs
  29. from glances.core.glances_autodiscover import GlancesAutoDiscoverServer
  30. from glances.core.glances_client import GlancesClient, GlancesClientTransport
  31. from glances.core.glances_logging import logger
  32. from glances.core.glances_passwordlist import GlancesPasswordList as GlancesPassword
  33. from glances.core.glances_staticlist import GlancesStaticServer
  34. from glances.outputs.glances_curses import GlancesCursesBrowser
  35. class GlancesClientBrowser(object):
  36. """This class creates and manages the TCP client browser (servers list)."""
  37. def __init__(self, config=None, args=None):
  38. # Store the arg/config
  39. self.args = args
  40. self.config = config
  41. self.static_server = None
  42. self.password = None
  43. # Load the configuration file
  44. self.load()
  45. # Start the autodiscover mode (Zeroconf listener)
  46. if not self.args.disable_autodiscover:
  47. self.autodiscover_server = GlancesAutoDiscoverServer()
  48. else:
  49. self.autodiscover_server = None
  50. # Init screen
  51. self.screen = GlancesCursesBrowser(args=self.args)
  52. def load(self):
  53. """Load server and password list from the confiuration file."""
  54. # Init the static server list (if defined)
  55. self.static_server = GlancesStaticServer(config=self.config)
  56. # Init the password list (if defined)
  57. self.password = GlancesPassword(config=self.config)
  58. def get_servers_list(self):
  59. """Return the current server list (list of dict).
  60. Merge of static + autodiscover servers list.
  61. """
  62. ret = []
  63. if self.args.browser:
  64. ret = self.static_server.get_servers_list()
  65. if self.autodiscover_server is not None:
  66. ret = self.static_server.get_servers_list() + self.autodiscover_server.get_servers_list()
  67. return ret
  68. def __get_uri(self, server):
  69. """Return the URI for the given server dict."""
  70. # Select the connection mode (with or without password)
  71. if server['password'] != "":
  72. if server['status'] == 'PROTECTED':
  73. # Try with the preconfigure password (only if status is PROTECTED)
  74. clear_password = self.password.get_password(server['name'])
  75. if clear_password is not None:
  76. server['password'] = self.password.sha256_hash(clear_password)
  77. return 'http://{0}:{1}@{2}:{3}'.format(server['username'], server['password'],
  78. server['ip'], server['port'])
  79. else:
  80. return 'http://{0}:{1}'.format(server['ip'], server['port'])
  81. def __serve_forever(self):
  82. """Main client loop."""
  83. while True:
  84. # No need to update the server list
  85. # It's done by the GlancesAutoDiscoverListener class (glances_autodiscover.py)
  86. # Or define staticaly in the configuration file (module glances_staticlist.py)
  87. # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
  88. # logger.debug(self.get_servers_list())
  89. try:
  90. for v in self.get_servers_list():
  91. # Do not retreive stats for statics server
  92. # Why ? Because for each offline servers, the timeout will be reached
  93. # So ? The curse interface freezes
  94. if v['type'] == 'STATIC' and v['status'] in ['UNKNOWN', 'SNMP', 'OFFLINE']:
  95. continue
  96. # Get the server URI
  97. uri = self.__get_uri(v)
  98. # Try to connect to the server
  99. t = GlancesClientTransport()
  100. t.set_timeout(3)
  101. # Get common stats
  102. try:
  103. s = ServerProxy(uri, transport=t)
  104. except Exception as e:
  105. logger.warning(
  106. "Client browser couldn't create socket {0}: {1}".format(uri, e))
  107. else:
  108. # Mandatory stats
  109. try:
  110. # CPU%
  111. cpu_percent = 100 - json.loads(s.getCpu())['idle']
  112. v['cpu_percent'] = '{0:.1f}'.format(cpu_percent)
  113. # MEM%
  114. v['mem_percent'] = json.loads(s.getMem())['percent']
  115. # OS (Human Readable name)
  116. v['hr_name'] = json.loads(s.getSystem())['hr_name']
  117. except (socket.error, Fault, KeyError) as e:
  118. logger.debug(
  119. "Error while grabbing stats form {0}: {1}".format(uri, e))
  120. v['status'] = 'OFFLINE'
  121. except ProtocolError as e:
  122. if str(e).find(" 401 ") > 0:
  123. # Error 401 (Authentication failed)
  124. # Password is not the good one...
  125. v['password'] = None
  126. v['status'] = 'PROTECTED'
  127. else:
  128. v['status'] = 'OFFLINE'
  129. logger.debug(
  130. "Cannot grab stats from {0}: {1}".format(uri, e))
  131. else:
  132. # Status
  133. v['status'] = 'ONLINE'
  134. # Optional stats (load is not available on Windows OS)
  135. try:
  136. # LOAD
  137. load_min5 = json.loads(s.getLoad())['min5']
  138. v['load_min5'] = '{0:.2f}'.format(load_min5)
  139. except Exception as e:
  140. logger.warning(
  141. "Error while grabbing stats form {0}: {1}".format(uri, e))
  142. # List can change size during iteration...
  143. except RuntimeError:
  144. logger.debug(
  145. "Server list dictionnary change inside the loop (wait next update)")
  146. # Update the screen (list or Glances client)
  147. if self.screen.active_server is None:
  148. # Display the Glances browser
  149. self.screen.update(self.get_servers_list())
  150. else:
  151. # Display the Glances client for the selected server
  152. logger.debug("Selected server: {0}".format(self.get_servers_list()[self.screen.active_server]))
  153. # Connection can take time
  154. # Display a popup
  155. self.screen.display_popup(
  156. 'Connect to {0}:{1}'.format(v['name'], v['port']), duration=1)
  157. # A password is needed to access to the server's stats
  158. if self.get_servers_list()[self.screen.active_server]['password'] is None:
  159. # First of all, check if a password is available in the [passwords] section
  160. clear_password = self.password.get_password(v['name'])
  161. if (clear_password is None or self.get_servers_list()
  162. [self.screen.active_server]['status'] == 'PROTECTED'):
  163. # Else, the password should be enter by the user
  164. # Display a popup to enter password
  165. clear_password = self.screen.display_popup(
  166. 'Password needed for {0}: '.format(v['name']), is_input=True)
  167. # Store the password for the selected server
  168. if clear_password is not None:
  169. self.set_in_selected('password', self.password.sha256_hash(clear_password))
  170. # Display the Glance client on the selected server
  171. logger.info("Connect Glances client to the {0} server".format(
  172. self.get_servers_list()[self.screen.active_server]['key']))
  173. # Init the client
  174. args_server = self.args
  175. # Overwrite connection setting
  176. args_server.client = self.get_servers_list()[self.screen.active_server]['ip']
  177. args_server.port = self.get_servers_list()[self.screen.active_server]['port']
  178. args_server.username = self.get_servers_list()[self.screen.active_server]['username']
  179. args_server.password = self.get_servers_list()[self.screen.active_server]['password']
  180. client = GlancesClient(config=self.config, args=args_server, return_to_browser=True)
  181. # Test if client and server are in the same major version
  182. if not client.login():
  183. self.screen.display_popup(
  184. "Sorry, cannot connect to '{0}'\n"
  185. "See 'glances.log' for more details".format(v['name']))
  186. # Set the ONLINE status for the selected server
  187. self.set_in_selected('status', 'OFFLINE')
  188. else:
  189. # Start the client loop
  190. # Return connection type: 'glances' or 'snmp'
  191. connection_type = client.serve_forever()
  192. try:
  193. logger.debug("Disconnect Glances client from the {0} server".format(
  194. self.get_servers_list()[self.screen.active_server]['key']))
  195. except IndexError:
  196. # Server did not exist anymore
  197. pass
  198. else:
  199. # Set the ONLINE status for the selected server
  200. if connection_type == 'snmp':
  201. self.set_in_selected('status', 'SNMP')
  202. else:
  203. self.set_in_selected('status', 'ONLINE')
  204. # Return to the browser (no server selected)
  205. self.screen.active_server = None
  206. def serve_forever(self):
  207. """Wrapper to the serve_forever function.
  208. This function will restore the terminal to a sane state
  209. before re-raising the exception and generating a traceback.
  210. """
  211. try:
  212. return self.__serve_forever()
  213. finally:
  214. self.end()
  215. def set_in_selected(self, key, value):
  216. """Set the (key, value) for the selected server in the list."""
  217. # Static list then dynamic one
  218. if self.screen.active_server >= len(self.static_server.get_servers_list()):
  219. self.autodiscover_server.set_server(
  220. self.screen.active_server - len(self.static_server.get_servers_list()),
  221. key, value)
  222. else:
  223. self.static_server.set_server(self.screen.active_server, key, value)
  224. def end(self):
  225. """End of the client browser session."""
  226. self.screen.end()