Completed
Push — master ( 7fc0f8...8b57d9 )
by Nicolas
37s
created

GlancesClient   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 226
rs 8.2769
wmc 41

12 Methods

Rating   Name   Duplication   Size   Complexity  
A _login_snmp() 0 14 2
A log_and_exit() 0 7 2
A client_mode() 0 4 1
A update_glances() 0 20 4
B __init__() 0 29 3
C _login_glances() 0 37 7
B serve_forever() 0 28 6
A update() 0 10 3
B login() 0 30 6
A quiet() 0 3 1
A update_snmp() 0 16 3
A end() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like GlancesClient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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."""
21
22
import json
23
import socket
24
import sys
25
26
from glances import __version__
27
from glances.compat import Fault, ProtocolError, ServerProxy, Transport
28
from glances.logger import logger
29
from glances.stats_client import GlancesStatsClient
30
from glances.outputs.glances_curses import GlancesCursesClient
31
32
33
class GlancesClientTransport(Transport):
34
35
    """This class overwrite the default XML-RPC transport and manage timeout."""
36
37
    def set_timeout(self, timeout):
38
        self.timeout = timeout
39
40
41
class GlancesClient(object):
42
43
    """This class creates and manages the TCP client."""
44
45
    def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
46
        # Store the arg/config
47
        self.args = args
48
        self.config = config
49
        # Quiet mode
50
        self._quiet = args.quiet
51
52
        # Default client mode
53
        self._client_mode = 'glances'
54
55
        # Return to browser or exit
56
        self.return_to_browser = return_to_browser
57
58
        # Build the URI
59
        if args.password != "":
60
            self.uri = 'http://{}:{}@{}:{}'.format(args.username, args.password,
61
                                                   args.client, args.port)
62
        else:
63
            self.uri = 'http://{}:{}'.format(args.client, args.port)
64
        logger.debug("Try to connect to {}".format(self.uri))
65
66
        # Try to connect to the URI
67
        transport = GlancesClientTransport()
68
        # Configure the server timeout
69
        transport.set_timeout(timeout)
70
        try:
71
            self.client = ServerProxy(self.uri, transport=transport)
72
        except Exception as e:
73
            self.log_and_exit("Client couldn't create socket {}: {}".format(self.uri, e))
74
75
    @property
76
    def quiet(self):
77
        return self._quiet
78
79
    def log_and_exit(self, msg=''):
80
        """Log and exit."""
81
        if not self.return_to_browser:
82
            logger.critical(msg)
83
            sys.exit(2)
84
        else:
85
            logger.error(msg)
86
87
    @property
88
    def client_mode(self):
89
        """Get the client mode."""
90
        return self._client_mode
91
92
    @client_mode.setter
93
    def client_mode(self, mode):
94
        """Set the client mode.
95
96
        - 'glances' = Glances server (default)
97
        - 'snmp' = SNMP (fallback)
98
        """
99
        self._client_mode = mode
100
101
    def _login_glances(self):
102
        """Login to a Glances server"""
103
        client_version = None
104
        try:
105
            client_version = self.client.init()
106
        except socket.error as err:
107
            # Fallback to SNMP
108
            self.client_mode = 'snmp'
109
            logger.error("Connection to Glances server failed ({} {})".format(err.errno, err.strerror))
110
            fallbackmsg = 'No Glances server found on {}. Trying fallback to SNMP...'.format(self.uri)
111
            if not self.return_to_browser:
112
                print(fallbackmsg)
113
            else:
114
                logger.info(fallbackmsg)
115
        except ProtocolError as err:
116
            # Other errors
117
            msg = "Connection to server {} failed".format(self.uri)
118
            if err.errcode == 401:
119
                msg += " (Bad username/password)"
120
            else:
121
                msg += " ({} {})".format(err.errcode, err.errmsg)
122
            self.log_and_exit(msg)
123
            return False
124
125
        if self.client_mode == 'glances':
126
            # Check that both client and server are in the same major version
127
            if __version__.split('.')[0] == client_version.split('.')[0]:
128
                # Init stats
129
                self.stats = GlancesStatsClient(config=self.config, args=self.args)
130
                self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
131
                logger.debug("Client version: {} / Server version: {}".format(__version__, client_version))
132
            else:
133
                self.log_and_exit(('Client and server not compatible: '
134
                                   'Client version: {} / Server version: {}'.format(__version__, client_version)))
135
                return False
136
137
        return True
138
139
    def _login_snmp(self):
140
        """Login to a SNMP server"""
141
        logger.info("Trying to grab stats by SNMP...")
142
143
        from glances.stats_client_snmp import GlancesStatsClientSNMP
144
145
        # Init stats
146
        self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args)
147
148
        if not self.stats.check_snmp():
149
            self.log_and_exit("Connection to SNMP server failed")
150
            return False
151
152
        return True
153
154
    def login(self):
155
        """Logon to the server."""
156
157
        if self.args.snmp_force:
158
            # Force SNMP instead of Glances server
159
            self.client_mode = 'snmp'
160
        else:
161
            # First of all, trying to connect to a Glances server
162
            if not self._login_glances():
163
                return False
164
165
        # Try SNMP mode
166
        if self.client_mode == 'snmp':
167
            if not self._login_snmp():
168
                return False
169
170
        # Load limits from the configuration file
171
        # Each client can choose its owns limits
172
        logger.debug("Load limits from the client configuration file")
173
        self.stats.load_limits(self.config)
174
175
        # Init screen
176
        if self.quiet:
177
            # In quiet mode, nothing is displayed
178
            logger.info("Quiet mode is ON: Nothing will be displayed")
179
        else:
180
            self.screen = GlancesCursesClient(config=self.config, args=self.args)
181
182
        # Return True: OK
183
        return True
184
185
    def update(self):
186
        """Update stats from Glances/SNMP server."""
187
        if self.client_mode == 'glances':
188
            return self.update_glances()
189
        elif self.client_mode == 'snmp':
190
            return self.update_snmp()
191
        else:
192
            self.end()
193
            logger.critical("Unknown server mode: {}".format(self.client_mode))
194
            sys.exit(2)
195
196
    def update_glances(self):
197
        """Get stats from Glances server.
198
199
        Return the client/server connection status:
200
        - Connected: Connection OK
201
        - Disconnected: Connection NOK
202
        """
203
        # Update the stats
204
        try:
205
            server_stats = json.loads(self.client.getAll())
206
        except socket.error:
207
            # Client cannot get server stats
208
            return "Disconnected"
209
        except Fault:
210
            # Client cannot get server stats (issue #375)
211
            return "Disconnected"
212
        else:
213
            # Put it in the internal dict
214
            self.stats.update(server_stats)
215
            return "Connected"
216
217
    def update_snmp(self):
218
        """Get stats from SNMP server.
219
220
        Return the client/server connection status:
221
        - SNMP: Connection with SNMP server OK
222
        - Disconnected: Connection NOK
223
        """
224
        # Update the stats
225
        try:
226
            self.stats.update()
227
        except Exception:
228
            # Client cannot get SNMP server stats
229
            return "Disconnected"
230
        else:
231
            # Grab success
232
            return "SNMP"
233
234
    def serve_forever(self):
235
        """Main client loop."""
236
237
        # Test if client and server are in the same major version
238
        if not self.login():
239
            logger.critical("The server version is not compatible with the client")
240
            self.end()
241
            return self.client_mode
242
243
        exitkey = False
244
        try:
245
            while True and not exitkey:
246
                # Update the stats
247
                cs_status = self.update()
248
249
                # Update the screen
250
                if not self.quiet:
251
                    exitkey = self.screen.update(self.stats,
252
                                                 cs_status=cs_status,
253
                                                 return_to_browser=self.return_to_browser)
254
255
                # Export stats using export modules
256
                self.stats.export(self.stats)
257
        except Exception as e:
258
            logger.critical(e)
259
            self.end()
260
261
        return self.client_mode
262
263
    def end(self):
264
        """End of the client session."""
265
        if not self.quiet:
266
            self.screen.end()
267