Checks whether an import is not accompanied by ``from __future__ import absolute_import`` (default behaviour in Python 3)
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 browser (list of Glances server).""" |
||
21 | |||
22 | import json |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
23 | import socket |
||
0 ignored issues
–
show
|
|||
24 | import threading |
||
0 ignored issues
–
show
|
|||
25 | |||
26 | from glances.compat import Fault, ProtocolError, ServerProxy |
||
0 ignored issues
–
show
|
|||
27 | from glances.client import GlancesClient, GlancesClientTransport |
||
0 ignored issues
–
show
|
|||
28 | from glances.logger import logger, LOG_FILENAME |
||
0 ignored issues
–
show
|
|||
29 | from glances.password_list import GlancesPasswordList as GlancesPassword |
||
0 ignored issues
–
show
|
|||
30 | from glances.static_list import GlancesStaticServer |
||
0 ignored issues
–
show
|
|||
31 | from glances.autodiscover import GlancesAutoDiscoverServer |
||
0 ignored issues
–
show
|
|||
32 | from glances.outputs.glances_curses_browser import GlancesCursesBrowser |
||
0 ignored issues
–
show
|
|||
33 | |||
34 | |||
35 | class GlancesClientBrowser(object): |
||
36 | |||
37 | """This class creates and manages the TCP client browser (servers list).""" |
||
38 | |||
39 | def __init__(self, config=None, args=None): |
||
40 | # Store the arg/config |
||
41 | self.args = args |
||
42 | self.config = config |
||
43 | self.static_server = None |
||
44 | self.password = None |
||
45 | |||
46 | # Load the configuration file |
||
47 | self.load() |
||
48 | |||
49 | # Start the autodiscover mode (Zeroconf listener) |
||
50 | if not self.args.disable_autodiscover: |
||
51 | self.autodiscover_server = GlancesAutoDiscoverServer() |
||
52 | else: |
||
53 | self.autodiscover_server = None |
||
54 | |||
55 | # Init screen |
||
56 | self.screen = GlancesCursesBrowser(args=self.args) |
||
57 | |||
58 | def load(self): |
||
59 | """Load server and password list from the confiuration file.""" |
||
60 | # Init the static server list (if defined) |
||
61 | self.static_server = GlancesStaticServer(config=self.config) |
||
62 | |||
63 | # Init the password list (if defined) |
||
64 | self.password = GlancesPassword(config=self.config) |
||
65 | |||
66 | def get_servers_list(self): |
||
67 | """Return the current server list (list of dict). |
||
68 | |||
69 | Merge of static + autodiscover servers list. |
||
70 | """ |
||
71 | ret = [] |
||
72 | |||
73 | if self.args.browser: |
||
74 | ret = self.static_server.get_servers_list() |
||
75 | if self.autodiscover_server is not None: |
||
76 | ret = self.static_server.get_servers_list() + self.autodiscover_server.get_servers_list() |
||
77 | |||
78 | return ret |
||
79 | |||
80 | def __get_uri(self, server): |
||
81 | """Return the URI for the given server dict.""" |
||
82 | # Select the connection mode (with or without password) |
||
83 | if server['password'] != "": |
||
84 | if server['status'] == 'PROTECTED': |
||
85 | # Try with the preconfigure password (only if status is PROTECTED) |
||
86 | clear_password = self.password.get_password(server['name']) |
||
87 | if clear_password is not None: |
||
88 | server['password'] = self.password.sha256_hash(clear_password) |
||
89 | return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], |
||
90 | server['ip'], server['port']) |
||
91 | else: |
||
92 | return 'http://{}:{}'.format(server['ip'], server['port']) |
||
93 | |||
94 | def __update_stats(self, server): |
||
95 | """ |
||
96 | Update stats for the given server (picked from the server list) |
||
97 | """ |
||
98 | # Get the server URI |
||
99 | uri = self.__get_uri(server) |
||
100 | |||
101 | # Try to connect to the server |
||
102 | t = GlancesClientTransport() |
||
103 | t.set_timeout(3) |
||
104 | |||
105 | # Get common stats |
||
106 | try: |
||
107 | s = ServerProxy(uri, transport=t) |
||
108 | except Exception as e: |
||
109 | logger.warning( |
||
110 | "Client browser couldn't create socket {}: {}".format(uri, e)) |
||
111 | else: |
||
112 | # Mandatory stats |
||
113 | try: |
||
114 | # CPU% |
||
115 | cpu_percent = 100 - json.loads(s.getCpu())['idle'] |
||
116 | server['cpu_percent'] = '{:.1f}'.format(cpu_percent) |
||
117 | # MEM% |
||
118 | server['mem_percent'] = json.loads(s.getMem())['percent'] |
||
119 | # OS (Human Readable name) |
||
120 | server['hr_name'] = json.loads(s.getSystem())['hr_name'] |
||
121 | except (socket.error, Fault, KeyError) as e: |
||
122 | logger.debug( |
||
123 | "Error while grabbing stats form {}: {}".format(uri, e)) |
||
124 | server['status'] = 'OFFLINE' |
||
125 | except ProtocolError as e: |
||
126 | if e.errcode == 401: |
||
127 | # Error 401 (Authentication failed) |
||
128 | # Password is not the good one... |
||
129 | server['password'] = None |
||
130 | server['status'] = 'PROTECTED' |
||
131 | else: |
||
132 | server['status'] = 'OFFLINE' |
||
133 | logger.debug("Cannot grab stats from {} ({} {})".format(uri, e.errcode, e.errmsg)) |
||
134 | else: |
||
135 | # Status |
||
136 | server['status'] = 'ONLINE' |
||
137 | |||
138 | # Optional stats (load is not available on Windows OS) |
||
139 | try: |
||
140 | # LOAD |
||
141 | load_min5 = json.loads(s.getLoad())['min5'] |
||
142 | server['load_min5'] = '{:.2f}'.format(load_min5) |
||
143 | except Exception as e: |
||
144 | logger.warning( |
||
145 | "Error while grabbing stats form {}: {}".format(uri, e)) |
||
146 | |||
147 | return server |
||
148 | |||
149 | def __display_server(self, server): |
||
150 | """ |
||
151 | Connect and display the given server |
||
152 | """ |
||
153 | # Display the Glances client for the selected server |
||
154 | logger.debug("Selected server {}".format(server)) |
||
155 | |||
156 | # Connection can take time |
||
157 | # Display a popup |
||
158 | self.screen.display_popup( |
||
159 | 'Connect to {}:{}'.format(server['name'], server['port']), duration=1) |
||
160 | |||
161 | # A password is needed to access to the server's stats |
||
162 | if server['password'] is None: |
||
163 | # First of all, check if a password is available in the [passwords] section |
||
164 | clear_password = self.password.get_password(server['name']) |
||
165 | if (clear_password is None or self.get_servers_list() |
||
166 | [self.screen.active_server]['status'] == 'PROTECTED'): |
||
167 | # Else, the password should be enter by the user |
||
168 | # Display a popup to enter password |
||
169 | clear_password = self.screen.display_popup( |
||
170 | 'Password needed for {}: '.format(server['name']), is_input=True) |
||
171 | # Store the password for the selected server |
||
172 | if clear_password is not None: |
||
173 | self.set_in_selected('password', self.password.sha256_hash(clear_password)) |
||
174 | |||
175 | # Display the Glance client on the selected server |
||
176 | logger.info("Connect Glances client to the {} server".format(server['key'])) |
||
177 | |||
178 | # Init the client |
||
179 | args_server = self.args |
||
180 | |||
181 | # Overwrite connection setting |
||
182 | args_server.client = server['ip'] |
||
183 | args_server.port = server['port'] |
||
184 | args_server.username = server['username'] |
||
185 | args_server.password = server['password'] |
||
186 | client = GlancesClient(config=self.config, args=args_server, return_to_browser=True) |
||
187 | |||
188 | # Test if client and server are in the same major version |
||
189 | if not client.login(): |
||
190 | self.screen.display_popup( |
||
191 | "Sorry, cannot connect to '{}'\n" |
||
192 | "See '{}' for more details".format(server['name'], LOG_FILENAME)) |
||
193 | |||
194 | # Set the ONLINE status for the selected server |
||
195 | self.set_in_selected('status', 'OFFLINE') |
||
196 | else: |
||
197 | # Start the client loop |
||
198 | # Return connection type: 'glances' or 'snmp' |
||
199 | connection_type = client.serve_forever() |
||
200 | |||
201 | try: |
||
202 | logger.debug("Disconnect Glances client from the {} server".format(server['key'])) |
||
203 | except IndexError: |
||
204 | # Server did not exist anymore |
||
205 | pass |
||
206 | else: |
||
207 | # Set the ONLINE status for the selected server |
||
208 | if connection_type == 'snmp': |
||
209 | self.set_in_selected('status', 'SNMP') |
||
210 | else: |
||
211 | self.set_in_selected('status', 'ONLINE') |
||
212 | |||
213 | # Return to the browser (no server selected) |
||
214 | self.screen.active_server = None |
||
215 | |||
216 | def __serve_forever(self): |
||
217 | """Main client loop.""" |
||
218 | # No need to update the server list |
||
219 | # It's done by the GlancesAutoDiscoverListener class (autodiscover.py) |
||
220 | # Or define staticaly in the configuration file (module static_list.py) |
||
221 | # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...) |
||
222 | thread_list = {} |
||
223 | while self.screen.is_end == False: |
||
224 | logger.debug("Iter through the following server list: {}".format(self.get_servers_list())) |
||
225 | for v in self.get_servers_list(): |
||
226 | key = v["key"] |
||
227 | thread = thread_list.get(key, None) |
||
228 | if thread is None or thread.is_alive() == False: |
||
229 | thread = threading.Thread(target=self.__update_stats, args=[v]) |
||
230 | thread_list[key] = thread |
||
231 | thread.start() |
||
232 | |||
233 | # Update the screen (list or Glances client) |
||
234 | if self.screen.active_server is None: |
||
235 | # Display the Glances browser |
||
236 | self.screen.update(self.get_servers_list()) |
||
237 | else: |
||
238 | # Display the active server |
||
239 | self.__display_server(self.get_servers_list()[self.screen.active_server]) |
||
240 | |||
241 | # exit key pressed |
||
242 | for thread in thread_list.values(): |
||
243 | thread.join() |
||
244 | |||
245 | def serve_forever(self): |
||
246 | """Wrapper to the serve_forever function. |
||
247 | |||
248 | This function will restore the terminal to a sane state |
||
249 | before re-raising the exception and generating a traceback. |
||
250 | """ |
||
251 | try: |
||
252 | return self.__serve_forever() |
||
253 | finally: |
||
254 | self.end() |
||
255 | |||
256 | def set_in_selected(self, key, value): |
||
257 | """Set the (key, value) for the selected server in the list.""" |
||
258 | # Static list then dynamic one |
||
259 | if self.screen.active_server >= len(self.static_server.get_servers_list()): |
||
260 | self.autodiscover_server.set_server( |
||
261 | self.screen.active_server - len(self.static_server.get_servers_list()), |
||
262 | key, value) |
||
263 | else: |
||
264 | self.static_server.set_server(self.screen.active_server, key, value) |
||
265 | |||
266 | def end(self): |
||
267 | """End of the client browser session.""" |
||
268 | self.screen.end() |
||
269 |