Completed
Push — master ( 2b80fa...6ea077 )
by Nicolas
01:22
created

GlancesClientBrowser.__update_stats()   C

Complexity

Conditions 8

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
c 2
b 0
f 0
dl 0
loc 54
rs 6.2167

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2017 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
23
import socket
24
import threading
25
26
from glances.compat import Fault, ProtocolError, ServerProxy
27
from glances.autodiscover import GlancesAutoDiscoverServer
28
from glances.client import GlancesClient, GlancesClientTransport
29
from glances.logger import logger, LOG_FILENAME
30
from glances.password_list import GlancesPasswordList as GlancesPassword
31
from glances.static_list import GlancesStaticServer
32
from glances.outputs.glances_curses_browser import GlancesCursesBrowser
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
223
        logger.debug("Iter through the following server list: {}".format(self.get_servers_list()))
224
        for v in self.get_servers_list():
225
            thread = threading.Thread(target=self.__update_stats, args=[v])
226
            thread.start()
227
228
        # Update the screen (list or Glances client)
229
        if self.screen.active_server is None:
230
            #  Display the Glances browser
231
            self.screen.update(self.get_servers_list())
232
        else:
233
            # Display the active server
234
            self.__display_server(self.get_servers_list()[self.screen.active_server])
235
236
        # Loop
237
        self.__serve_forever()
238
239
    def serve_forever(self):
240
        """Wrapper to the serve_forever function.
241
242
        This function will restore the terminal to a sane state
243
        before re-raising the exception and generating a traceback.
244
        """
245
        try:
246
            return self.__serve_forever()
247
        finally:
248
            self.end()
249
250
    def set_in_selected(self, key, value):
251
        """Set the (key, value) for the selected server in the list."""
252
        # Static list then dynamic one
253
        if self.screen.active_server >= len(self.static_server.get_servers_list()):
254
            self.autodiscover_server.set_server(
255
                self.screen.active_server - len(self.static_server.get_servers_list()),
256
                key, value)
257
        else:
258
            self.static_server.set_server(self.screen.active_server, key, value)
259
260
    def end(self):
261
        """End of the client browser session."""
262
        self.screen.end()
263