Completed
Pull Request — master (#963)
by
unknown
01:02
created

GlancesClientBrowser.__display_server()   D

Complexity

Conditions 9

Size

Total Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 66
rs 4.5084

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