Completed
Push — master ( 3dcd25...20576f )
by Nicolas
01:26
created

GlancesClient   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 207
rs 8.8
wmc 36

11 Methods

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