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): |
||
0 ignored issues
–
show
best-practice
introduced
by
Loading history...
|
|||
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: |
||
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: |
||
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: |
||
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 |