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

glances/client_browser.py (49 issues)

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()
0 ignored issues
show
This line is too long as per the coding-style (105/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
89
            return 'http://{}:{}@{}:{}'.format(server['username'], server['password'],
0 ignored issues
show
This line is too long as per the coding-style (86/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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()
0 ignored issues
show
Coding Style Naming introduced by
The name t 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...
103
        t.set_timeout(3)
104
105
        # Get common stats
106
        try:
107
            s = ServerProxy(uri, transport=t)
0 ignored issues
show
Coding Style Naming introduced by
The name s 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...
108
        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...
Coding Style Naming introduced by
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...
109
            logger.warning(
110
                "Client browser couldn't create socket {}: {}".format(uri, e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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:
0 ignored issues
show
Coding Style Naming introduced by
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...
122
                logger.debug(
123
                    "Error while grabbing stats form {}: {}".format(uri, e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
124
                server['status'] = 'OFFLINE'
125
            except ProtocolError as e:
0 ignored issues
show
Coding Style Naming introduced by
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...
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))
0 ignored issues
show
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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:
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...
Coding Style Naming introduced by
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...
144
                    logger.warning(
145
                        "Error while grabbing stats form {}: {}".format(uri, e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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
0 ignored issues
show
This line is too long as per the coding-style (87/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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'):
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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))
0 ignored issues
show
This line is too long as per the coding-style (91/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
174
175
        # Display the Glance client on the selected server
176
        logger.info("Connect Glances client to the {} server".format(server['key']))
0 ignored issues
show
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (92/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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))
0 ignored issues
show
This line is too long as per the coding-style (81/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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']))
0 ignored issues
show
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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...)
0 ignored issues
show
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
222
        thread_list = {}
223
        while self.screen.is_end == False:
0 ignored issues
show
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
224
            logger.debug("Iter through the following server list: {}".format(self.get_servers_list()))
0 ignored issues
show
This line is too long as per the coding-style (102/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
225
            for v in self.get_servers_list():
0 ignored issues
show
Coding Style Naming introduced by
The name v 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...
226
                key = v["key"]
227
                thread = thread_list.get(key, None)
228
                if thread is None or thread.is_alive() == False:
0 ignored issues
show
Comparison to False should be 'not expr' or 'expr is False'
Loading history...
229
                    thread = threading.Thread(target=self.__update_stats, args=[v])
0 ignored issues
show
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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])
0 ignored issues
show
This line is too long as per the coding-style (89/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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()):
0 ignored issues
show
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
260
            self.autodiscover_server.set_server(
261
                self.screen.active_server - len(self.static_server.get_servers_list()),
0 ignored issues
show
This line is too long as per the coding-style (87/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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