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

glances/client.py (37 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
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
23
import socket
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
import sys
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
25
26
from glances import __version__
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
27
from glances.compat import Fault, ProtocolError, ServerProxy, Transport
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
28
from glances.logger import logger
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
29
from glances.stats_client import GlancesStatsClient
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
30
from glances.outputs.glances_curses import GlancesCursesClient
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
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):
0 ignored issues
show
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
38
        self.timeout = timeout
0 ignored issues
show
The attribute timeout was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
39
40
41
class GlancesClient(object):
0 ignored issues
show
Too many instance attributes (10/7)
Loading history...
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):
0 ignored issues
show
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
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))
0 ignored issues
show
This line is too long as per the coding-style (89/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
74
75
    @property
76
    def quiet(self):
0 ignored issues
show
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
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))
0 ignored issues
show
This line is too long as per the coding-style (103/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
110
            fallbackmsg = 'No Glances server found on {}. Trying fallback to SNMP...'.format(self.uri)
0 ignored issues
show
This line is too long as per the coding-style (102/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
111
            if not self.return_to_browser:
112
                print(fallbackmsg)
0 ignored issues
show
Unused Code Coding Style introduced by
Unnecessary parens after u'print' keyword
Loading history...
print statement used
Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
The attribute stats was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
130
                self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
131
                logger.debug("Client version: {} / Server version: {}".format(__version__, client_version))
0 ignored issues
show
This line is too long as per the coding-style (107/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
132
            else:
133
                self.log_and_exit(('Client and server not compatible: '
134
                                   'Client version: {} / Server version: {}'.format(__version__, client_version)))
0 ignored issues
show
This line is too long as per the coding-style (114/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
144
145
        # Init stats
146
        self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args)
0 ignored issues
show
The attribute stats was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (81/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
The attribute screen was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
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))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
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:
0 ignored issues
show
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
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")
0 ignored issues
show
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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)
0 ignored issues
show
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
254
255
                # Export stats using export modules
256
                self.stats.export(self.stats)
257
        except Exception as e:
0 ignored issues
show
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

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