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 |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
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 |
||
0 ignored issues
–
show
|
|||
30 | from glances.outputs.glances_curses import GlancesCursesClient |
||
0 ignored issues
–
show
|
|||
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): |
||
0 ignored issues
–
show
This method should have a docstring.
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods: class SomeClass:
def some_method(self):
"""Do x and return foo."""
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.
Loading history...
|
|||
38 | self.timeout = timeout |
||
0 ignored issues
–
show
|
|||
39 | |||
40 | |||
41 | class GlancesClient(object): |
||
0 ignored issues
–
show
|
|||
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): |
||
0 ignored issues
–
show
|
|||
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): |
||
0 ignored issues
–
show
This method should have a docstring.
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods: class SomeClass:
def some_method(self):
"""Do x and return foo."""
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.
Loading history...
|
|||
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) |
||
0 ignored issues
–
show
|
|||
111 | if not self.return_to_browser: |
||
112 | print(fallbackmsg) |
||
0 ignored issues
–
show
|
|||
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) |
||
0 ignored issues
–
show
|
|||
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 |
||
0 ignored issues
–
show
|
|||
144 | |||
145 | # Init stats |
||
146 | self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args) |
||
0 ignored issues
–
show
|
|||
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) |
||
0 ignored issues
–
show
|
|||
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") |
||
0 ignored issues
–
show
|
|||
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) |
||
0 ignored issues
–
show
|
|||
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...
The name
e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ ).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site.
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 |