Test Failed
Push — develop ( abf64f...1d1151 )
by Nicolas
02:59
created

glances/client_browser.py (10 issues)

Checks whether an import is not accompanied by ``from __future__ import absolute_import`` (default behaviour in Python 3)

Minor
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
import missing from __future__ import absolute_import
Loading history...
23
import socket
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
import threading
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
25
26
from glances.compat import Fault, ProtocolError, ServerProxy
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
27
from glances.client import GlancesClient, GlancesClientTransport
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
28
from glances.logger import logger, LOG_FILENAME
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
29
from glances.password_list import GlancesPasswordList as GlancesPassword
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
30
from glances.static_list import GlancesStaticServer
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
31
from glances.autodiscover import GlancesAutoDiscoverServer
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
32
from glances.outputs.glances_curses_browser import GlancesCursesBrowser
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
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