You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
8.6 KiB

4 years ago
  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of Glances.
  4. #
  5. # Copyright (C) 2018 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."""
  20. import json
  21. import socket
  22. import sys
  23. from glances import __version__
  24. from glances.compat import Fault, ProtocolError, ServerProxy, Transport
  25. from glances.logger import logger
  26. from glances.stats_client import GlancesStatsClient
  27. from glances.outputs.glances_curses import GlancesCursesClient
  28. class GlancesClientTransport(Transport):
  29. """This class overwrite the default XML-RPC transport and manage timeout."""
  30. def set_timeout(self, timeout):
  31. self.timeout = timeout
  32. class GlancesClient(object):
  33. """This class creates and manages the TCP client."""
  34. def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
  35. # Store the arg/config
  36. self.args = args
  37. self.config = config
  38. # Quiet mode
  39. self._quiet = args.quiet
  40. # Default client mode
  41. self._client_mode = 'glances'
  42. # Return to browser or exit
  43. self.return_to_browser = return_to_browser
  44. # Build the URI
  45. if args.password != "":
  46. self.uri = 'http://{}:{}@{}:{}'.format(args.username, args.password,
  47. args.client, args.port)
  48. else:
  49. self.uri = 'http://{}:{}'.format(args.client, args.port)
  50. logger.debug("Try to connect to {}".format(self.uri))
  51. # Try to connect to the URI
  52. transport = GlancesClientTransport()
  53. # Configure the server timeout
  54. transport.set_timeout(timeout)
  55. try:
  56. self.client = ServerProxy(self.uri, transport=transport)
  57. except Exception as e:
  58. self.log_and_exit("Client couldn't create socket {}: {}".format(self.uri, e))
  59. @property
  60. def quiet(self):
  61. return self._quiet
  62. def log_and_exit(self, msg=''):
  63. """Log and exit."""
  64. if not self.return_to_browser:
  65. logger.critical(msg)
  66. sys.exit(2)
  67. else:
  68. logger.error(msg)
  69. @property
  70. def client_mode(self):
  71. """Get the client mode."""
  72. return self._client_mode
  73. @client_mode.setter
  74. def client_mode(self, mode):
  75. """Set the client mode.
  76. - 'glances' = Glances server (default)
  77. - 'snmp' = SNMP (fallback)
  78. """
  79. self._client_mode = mode
  80. def _login_glances(self):
  81. """Login to a Glances server"""
  82. client_version = None
  83. try:
  84. client_version = self.client.init()
  85. except socket.error as err:
  86. # Fallback to SNMP
  87. self.client_mode = 'snmp'
  88. logger.error("Connection to Glances server failed ({} {})".format(err.errno, err.strerror))
  89. fallbackmsg = 'No Glances server found on {}. Trying fallback to SNMP...'.format(self.uri)
  90. if not self.return_to_browser:
  91. print(fallbackmsg)
  92. else:
  93. logger.info(fallbackmsg)
  94. except ProtocolError as err:
  95. # Other errors
  96. msg = "Connection to server {} failed".format(self.uri)
  97. if err.errcode == 401:
  98. msg += " (Bad username/password)"
  99. else:
  100. msg += " ({} {})".format(err.errcode, err.errmsg)
  101. self.log_and_exit(msg)
  102. return False
  103. if self.client_mode == 'glances':
  104. # Check that both client and server are in the same major version
  105. if __version__.split('.')[0] == client_version.split('.')[0]:
  106. # Init stats
  107. self.stats = GlancesStatsClient(config=self.config, args=self.args)
  108. self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
  109. logger.debug("Client version: {} / Server version: {}".format(__version__, client_version))
  110. else:
  111. self.log_and_exit(('Client and server not compatible: '
  112. 'Client version: {} / Server version: {}'.format(__version__, client_version)))
  113. return False
  114. return True
  115. def _login_snmp(self):
  116. """Login to a SNMP server"""
  117. logger.info("Trying to grab stats by SNMP...")
  118. from glances.stats_client_snmp import GlancesStatsClientSNMP
  119. # Init stats
  120. self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args)
  121. if not self.stats.check_snmp():
  122. self.log_and_exit("Connection to SNMP server failed")
  123. return False
  124. return True
  125. def login(self):
  126. """Logon to the server."""
  127. if self.args.snmp_force:
  128. # Force SNMP instead of Glances server
  129. self.client_mode = 'snmp'
  130. else:
  131. # First of all, trying to connect to a Glances server
  132. if not self._login_glances():
  133. return False
  134. # Try SNMP mode
  135. if self.client_mode == 'snmp':
  136. if not self._login_snmp():
  137. return False
  138. # Load limits from the configuration file
  139. # Each client can choose its owns limits
  140. logger.debug("Load limits from the client configuration file")
  141. self.stats.load_limits(self.config)
  142. # Init screen
  143. if self.quiet:
  144. # In quiet mode, nothing is displayed
  145. logger.info("Quiet mode is ON: Nothing will be displayed")
  146. else:
  147. self.screen = GlancesCursesClient(config=self.config, args=self.args)
  148. # Return True: OK
  149. return True
  150. def update(self):
  151. """Update stats from Glances/SNMP server."""
  152. if self.client_mode == 'glances':
  153. return self.update_glances()
  154. elif self.client_mode == 'snmp':
  155. return self.update_snmp()
  156. else:
  157. self.end()
  158. logger.critical("Unknown server mode: {}".format(self.client_mode))
  159. sys.exit(2)
  160. def update_glances(self):
  161. """Get stats from Glances server.
  162. Return the client/server connection status:
  163. - Connected: Connection OK
  164. - Disconnected: Connection NOK
  165. """
  166. # Update the stats
  167. try:
  168. server_stats = json.loads(self.client.getAll())
  169. except socket.error:
  170. # Client cannot get server stats
  171. return "Disconnected"
  172. except Fault:
  173. # Client cannot get server stats (issue #375)
  174. return "Disconnected"
  175. else:
  176. # Put it in the internal dict
  177. self.stats.update(server_stats)
  178. return "Connected"
  179. def update_snmp(self):
  180. """Get stats from SNMP server.
  181. Return the client/server connection status:
  182. - SNMP: Connection with SNMP server OK
  183. - Disconnected: Connection NOK
  184. """
  185. # Update the stats
  186. try:
  187. self.stats.update()
  188. except Exception:
  189. # Client cannot get SNMP server stats
  190. return "Disconnected"
  191. else:
  192. # Grab success
  193. return "SNMP"
  194. def serve_forever(self):
  195. """Main client loop."""
  196. # Test if client and server are in the same major version
  197. if not self.login():
  198. logger.critical("The server version is not compatible with the client")
  199. self.end()
  200. return self.client_mode
  201. exitkey = False
  202. try:
  203. while True and not exitkey:
  204. # Update the stats
  205. cs_status = self.update()
  206. # Update the screen
  207. if not self.quiet:
  208. exitkey = self.screen.update(self.stats,
  209. cs_status=cs_status,
  210. return_to_browser=self.return_to_browser)
  211. # Export stats using export modules
  212. self.stats.export(self.stats)
  213. except Exception as e:
  214. logger.critical(e)
  215. self.end()
  216. return self.client_mode
  217. def end(self):
  218. """End of the client session."""
  219. if not self.quiet:
  220. self.screen.end()