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