Test Failed
Push — master ( 7e7379...128504 )
by Nicolas
03:31
created

glances.client.GlancesClient.log_and_exit()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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