Test Failed
Push — develop ( d7cf39...faa4bd )
by Nicolas
04:34 queued 10s
created

glances/client.py (2 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 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:
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
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:
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
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