Completed
Push — master ( 1806d1...053f07 )
by Nicolas
01:42
created

GlancesClientBrowser   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 222
Duplicated Lines 0 %
Metric Value
dl 0
loc 222
rs 8.8
wmc 36

8 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 7 1
A __init__() 0 18 2
A __get_uri() 0 13 4
A serve_forever() 0 10 1
A set_in_selected() 0 9 2
A get_servers_list() 0 13 3
A end() 0 3 1
F __serve_forever() 0 138 22
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://{0}:{1}@{2}:{3}'.format(server['username'], server['password'],
89
                                                   server['ip'], server['port'])
90
        else:
91
            return 'http://{0}:{1}'.format(server['ip'], server['port'])
92
93
    def __serve_forever(self):
94
        """Main client loop."""
95
        while True:
96
            # No need to update the server list
97
            # It's done by the GlancesAutoDiscoverListener class (autodiscover.py)
98
            # Or define staticaly in the configuration file (module static_list.py)
99
            # For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
100
            # logger.debug(self.get_servers_list())
101
            try:
102
                for v in self.get_servers_list():
103
                    # Do not retreive stats for statics server
104
                    # Why ? Because for each offline servers, the timeout will be reached
105
                    # So ? The curse interface freezes
106
                    if v['type'] == 'STATIC' and v['status'] in ['UNKNOWN', 'SNMP', 'OFFLINE']:
107
                        continue
108
109
                    # Get the server URI
110
                    uri = self.__get_uri(v)
111
112
                    # Try to connect to the server
113
                    t = GlancesClientTransport()
114
                    t.set_timeout(3)
115
116
                    # Get common stats
117
                    try:
118
                        s = ServerProxy(uri, transport=t)
119
                    except Exception as e:
120
                        logger.warning(
121
                            "Client browser couldn't create socket {0}: {1}".format(uri, e))
122
                    else:
123
                        # Mandatory stats
124
                        try:
125
                            # CPU%
126
                            cpu_percent = 100 - json.loads(s.getCpu())['idle']
127
                            v['cpu_percent'] = '{0:.1f}'.format(cpu_percent)
128
                            # MEM%
129
                            v['mem_percent'] = json.loads(s.getMem())['percent']
130
                            # OS (Human Readable name)
131
                            v['hr_name'] = json.loads(s.getSystem())['hr_name']
132
                        except (socket.error, Fault, KeyError) as e:
133
                            logger.debug(
134
                                "Error while grabbing stats form {0}: {1}".format(uri, e))
135
                            v['status'] = 'OFFLINE'
136
                        except ProtocolError as e:
137
                            if e.errcode == 401:
138
                                # Error 401 (Authentication failed)
139
                                # Password is not the good one...
140
                                v['password'] = None
141
                                v['status'] = 'PROTECTED'
142
                            else:
143
                                v['status'] = 'OFFLINE'
144
                            logger.debug("Cannot grab stats from {0} ({1} {2})".format(uri, e.errcode, e.errmsg))
145
                        else:
146
                            # Status
147
                            v['status'] = 'ONLINE'
148
149
                            # Optional stats (load is not available on Windows OS)
150
                            try:
151
                                # LOAD
152
                                load_min5 = json.loads(s.getLoad())['min5']
153
                                v['load_min5'] = '{0:.2f}'.format(load_min5)
154
                            except Exception as e:
155
                                logger.warning(
156
                                    "Error while grabbing stats form {0}: {1}".format(uri, e))
157
            # List can change size during iteration...
158
            except RuntimeError:
159
                logger.debug(
160
                    "Server list dictionnary change inside the loop (wait next update)")
161
162
            # Update the screen (list or Glances client)
163
            if self.screen.active_server is None:
164
                #  Display the Glances browser
165
                self.screen.update(self.get_servers_list())
166
            else:
167
                # Display the Glances client for the selected server
168
                logger.debug("Selected server: {0}".format(self.get_servers_list()[self.screen.active_server]))
169
170
                # Connection can take time
171
                # Display a popup
172
                self.screen.display_popup(
173
                    'Connect to {0}:{1}'.format(v['name'], v['port']), duration=1)
174
175
                # A password is needed to access to the server's stats
176
                if self.get_servers_list()[self.screen.active_server]['password'] is None:
177
                    # First of all, check if a password is available in the [passwords] section
178
                    clear_password = self.password.get_password(v['name'])
179
                    if (clear_password is None or self.get_servers_list()
180
                            [self.screen.active_server]['status'] == 'PROTECTED'):
181
                        # Else, the password should be enter by the user
182
                        # Display a popup to enter password
183
                        clear_password = self.screen.display_popup(
184
                            'Password needed for {0}: '.format(v['name']), is_input=True)
185
                    # Store the password for the selected server
186
                    if clear_password is not None:
187
                        self.set_in_selected('password', self.password.sha256_hash(clear_password))
188
189
                # Display the Glance client on the selected server
190
                logger.info("Connect Glances client to the {0} server".format(
191
                    self.get_servers_list()[self.screen.active_server]['key']))
192
193
                # Init the client
194
                args_server = self.args
195
196
                # Overwrite connection setting
197
                args_server.client = self.get_servers_list()[self.screen.active_server]['ip']
198
                args_server.port = self.get_servers_list()[self.screen.active_server]['port']
199
                args_server.username = self.get_servers_list()[self.screen.active_server]['username']
200
                args_server.password = self.get_servers_list()[self.screen.active_server]['password']
201
                client = GlancesClient(config=self.config, args=args_server, return_to_browser=True)
202
203
                # Test if client and server are in the same major version
204
                if not client.login():
205
                    self.screen.display_popup(
206
                        "Sorry, cannot connect to '{0}'\n"
207
                        "See 'glances.log' for more details".format(v['name']))
208
209
                    # Set the ONLINE status for the selected server
210
                    self.set_in_selected('status', 'OFFLINE')
211
                else:
212
                    # Start the client loop
213
                    # Return connection type: 'glances' or 'snmp'
214
                    connection_type = client.serve_forever()
215
216
                    try:
217
                        logger.debug("Disconnect Glances client from the {0} server".format(
218
                            self.get_servers_list()[self.screen.active_server]['key']))
219
                    except IndexError:
220
                        # Server did not exist anymore
221
                        pass
222
                    else:
223
                        # Set the ONLINE status for the selected server
224
                        if connection_type == 'snmp':
225
                            self.set_in_selected('status', 'SNMP')
226
                        else:
227
                            self.set_in_selected('status', 'ONLINE')
228
229
                # Return to the browser (no server selected)
230
                self.screen.active_server = None
231
232
    def serve_forever(self):
233
        """Wrapper to the serve_forever function.
234
235
        This function will restore the terminal to a sane state
236
        before re-raising the exception and generating a traceback.
237
        """
238
        try:
239
            return self.__serve_forever()
240
        finally:
241
            self.end()
242
243
    def set_in_selected(self, key, value):
244
        """Set the (key, value) for the selected server in the list."""
245
        # Static list then dynamic one
246
        if self.screen.active_server >= len(self.static_server.get_servers_list()):
247
            self.autodiscover_server.set_server(
248
                self.screen.active_server - len(self.static_server.get_servers_list()),
249
                key, value)
250
        else:
251
            self.static_server.set_server(self.screen.active_server, key, value)
252
253
    def end(self):
254
        """End of the client browser session."""
255
        self.screen.end()
256