Test Failed
Push — develop ( abf64f...1d1151 )
by Nicolas
02:59
created

glances/autodiscover.py (54 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 autodiscover Glances server (thk to the ZeroConf protocol)."""
21
22
import socket
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
23
import sys
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
25
from glances.globals import BSD
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
26
from glances.logger import logger
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
27
28
try:
29
    from zeroconf import (
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
30
        __version__ as __zeroconf_version,
31
        ServiceBrowser,
32
        ServiceInfo,
33
        Zeroconf
34
    )
35
    zeroconf_tag = True
0 ignored issues
show
Coding Style Naming introduced by
The name zeroconf_tag does not conform to the constant naming conventions ((([A-Z_][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...
36
except ImportError:
37
    zeroconf_tag = False
0 ignored issues
show
Coding Style Naming introduced by
The name zeroconf_tag does not conform to the constant naming conventions ((([A-Z_][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...
38
39
# Zeroconf 0.17 or higher is needed
40
if zeroconf_tag:
41
    zeroconf_min_version = (0, 17, 0)
0 ignored issues
show
Coding Style Naming introduced by
The name zeroconf_min_version does not conform to the constant naming conventions ((([A-Z_][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...
42
    zeroconf_version = tuple([int(num) for num in __zeroconf_version.split('.')])
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...
Coding Style Naming introduced by
The name zeroconf_version does not conform to the constant naming conventions ((([A-Z_][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...
43
    logger.debug("Zeroconf version {} detected.".format(__zeroconf_version))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
44
    if zeroconf_version < zeroconf_min_version:
45
        logger.critical("Please install zeroconf 0.17 or higher.")
46
        sys.exit(1)
47
48
# Global var
49
# Recent versions of the zeroconf python package doesnt like a zeroconf type that ends with '._tcp.'.
0 ignored issues
show
This line is too long as per the coding-style (101/80).

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

Loading history...
50
# Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % 'glances' #888
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...
51
zeroconf_type = "_%s._tcp.local." % 'glances'
0 ignored issues
show
Coding Style Naming introduced by
The name zeroconf_type does not conform to the constant naming conventions ((([A-Z_][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...
52
53
54
class AutoDiscovered(object):
55
56
    """Class to manage the auto discovered servers dict."""
57
58
    def __init__(self):
59
        # server_dict is a list of dict (JSON compliant)
60
        # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ]
0 ignored issues
show
This line is too long as per the coding-style (100/80).

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

Loading history...
61
        self._server_list = []
62
63
    def get_servers_list(self):
64
        """Return the current server list (list of dict)."""
65
        return self._server_list
66
67
    def set_server(self, server_pos, key, value):
68
        """Set the key to the value for the server_pos (position in the list)."""
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...
69
        self._server_list[server_pos][key] = value
70
71
    def add_server(self, name, ip, port):
0 ignored issues
show
Coding Style Naming introduced by
The name ip does not conform to the argument 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...
72
        """Add a new server to the list."""
73
        new_server = {
74
            'key': name,  # Zeroconf name with both hostname and port
75
            'name': name.split(':')[0],  # Short name
76
            'ip': ip,  # IP address seen by the client
77
            'port': port,  # TCP port
78
            'username': 'glances',  # Default username
79
            'password': '',  # Default password
80
            'status': 'UNKNOWN',  # Server status: 'UNKNOWN', 'OFFLINE', 'ONLINE', 'PROTECTED'
0 ignored issues
show
This line is too long as per the coding-style (94/80).

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

Loading history...
81
            'type': 'DYNAMIC'}  # Server type: 'STATIC' or 'DYNAMIC'
82
        self._server_list.append(new_server)
83
        logger.debug("Updated servers list (%s servers): %s" %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
84
                     (len(self._server_list), self._server_list))
85
86
    def remove_server(self, name):
87
        """Remove a server from the dict."""
88
        for i in self._server_list:
89
            if i['key'] == name:
90
                try:
91
                    self._server_list.remove(i)
92
                    logger.debug("Remove server %s from the list" % name)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
93
                    logger.debug("Updated servers list (%s servers): %s" % (
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
94
                        len(self._server_list), self._server_list))
95
                except ValueError:
96
                    logger.error(
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
97
                        "Cannot remove server %s from the list" % name)
98
99
100
class GlancesAutoDiscoverListener(object):
101
102
    """Zeroconf listener for Glances server."""
103
104
    def __init__(self):
105
        # Create an instance of the servers list
106
        self.servers = AutoDiscovered()
107
108
    def get_servers_list(self):
109
        """Return the current server list (list of dict)."""
110
        return self.servers.get_servers_list()
111
112
    def set_server(self, server_pos, key, value):
113
        """Set the key to the value for the server_pos (position in the list)."""
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...
114
        self.servers.set_server(server_pos, key, value)
115
116
    def add_service(self, zeroconf, srv_type, srv_name):
117
        """Method called when a new Zeroconf client is detected.
118
119
        Return True if the zeroconf client is a Glances server
120
        Note: the return code will never be used
121
        """
122
        if srv_type != zeroconf_type:
123
            return False
124
        logger.debug("Check new Zeroconf server: %s / %s" %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
125
                     (srv_type, srv_name))
126
        info = zeroconf.get_service_info(srv_type, srv_name)
127
        if info:
128
            new_server_ip = socket.inet_ntoa(info.address)
129
            new_server_port = info.port
130
131
            # Add server to the global dict
132
            self.servers.add_server(srv_name, new_server_ip, new_server_port)
133
            logger.info("New Glances server detected (%s from %s:%s)" %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
134
                        (srv_name, new_server_ip, new_server_port))
135
        else:
136
            logger.warning(
137
                "New Glances server detected, but Zeroconf info failed to be grabbed")
0 ignored issues
show
This line is too long as per the coding-style (86/80).

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

Loading history...
138
        return True
139
140
    def remove_service(self, zeroconf, srv_type, srv_name):
0 ignored issues
show
The argument zeroconf seems to be unused.
Loading history...
The argument srv_type seems to be unused.
Loading history...
141
        """Remove the server from the list."""
142
        self.servers.remove_server(srv_name)
143
        logger.info(
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
144
            "Glances server %s removed from the autodetect list" % srv_name)
145
146
147
class GlancesAutoDiscoverServer(object):
148
149
    """Implementation of the Zeroconf protocol (server side for the Glances client)."""
0 ignored issues
show
This line is too long as per the coding-style (87/80).

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

Loading history...
150
151
    def __init__(self, args=None):
0 ignored issues
show
The argument args seems to be unused.
Loading history...
152
        if zeroconf_tag:
153
            logger.info("Init autodiscover mode (Zeroconf protocol)")
154
            try:
155
                self.zeroconf = Zeroconf()
156
            except socket.error 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...
157
                logger.error("Cannot start Zeroconf (%s)" % e)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
158
                self.zeroconf_enable_tag = False
159
            else:
160
                self.listener = GlancesAutoDiscoverListener()
161
                self.browser = ServiceBrowser(
162
                    self.zeroconf, zeroconf_type, self.listener)
163
                self.zeroconf_enable_tag = True
164
        else:
165
            logger.error("Cannot start autodiscover mode (Zeroconf lib is not installed)")
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...
166
            self.zeroconf_enable_tag = False
167
168
    def get_servers_list(self):
169
        """Return the current server list (dict of dict)."""
170
        if zeroconf_tag and self.zeroconf_enable_tag:
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
171
            return self.listener.get_servers_list()
172
        else:
173
            return []
174
175
    def set_server(self, server_pos, key, value):
176
        """Set the key to the value for the server_pos (position in the list)."""
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...
177
        if zeroconf_tag and self.zeroconf_enable_tag:
178
            self.listener.set_server(server_pos, key, value)
179
180
    def close(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...
181
        if zeroconf_tag and self.zeroconf_enable_tag:
182
            self.zeroconf.close()
183
184
185
class GlancesAutoDiscoverClient(object):
186
187
    """Implementation of the zeroconf protocol (client side for the Glances server)."""
0 ignored issues
show
This line is too long as per the coding-style (87/80).

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

Loading history...
188
189
    def __init__(self, hostname, args=None):
190
        if zeroconf_tag:
191
            zeroconf_bind_address = args.bind_address
192
            try:
193
                self.zeroconf = Zeroconf()
194
            except socket.error 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...
195
                logger.error("Cannot start zeroconf: {}".format(e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
196
197
            # XXX *BSDs: Segmentation fault (core dumped)
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
198
            # -- https://bitbucket.org/al45tair/netifaces/issues/15
199
            if not BSD:
200
                try:
201
                    # -B @ overwrite the dynamic IPv4 choice
202
                    if zeroconf_bind_address == '0.0.0.0':
203
                        zeroconf_bind_address = self.find_active_ip_address()
204
                except KeyError:
205
                    # Issue #528 (no network interface available)
206
                    pass
207
208
            # Check IP v4/v6
209
            address_family = socket.getaddrinfo(zeroconf_bind_address, args.port)[0][0]
0 ignored issues
show
This line is too long as per the coding-style (87/80).

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

Loading history...
210
211
            # Start the zeroconf service
212
            self.info = ServiceInfo(
213
                zeroconf_type, '{}:{}.{}'.format(hostname, args.port, zeroconf_type),
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
214
                address=socket.inet_pton(address_family, zeroconf_bind_address),
215
                port=args.port, weight=0, priority=0, properties={}, server=hostname)
0 ignored issues
show
This line is too long as per the coding-style (85/80).

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

Loading history...
216
            try:
217
                self.zeroconf.register_service(self.info)
218
            except socket.error 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...
219
                logger.error("Error while announcing Glances server: {}".format(e))
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...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
220
            else:
221
                print("Announce the Glances server on the LAN (using {} IP address)".format(zeroconf_bind_address))
0 ignored issues
show
This line is too long as per the coding-style (115/80).

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

Loading history...
Unused Code Coding Style introduced by
Unnecessary parens after u'print' keyword
Loading history...
print statement used
Loading history...
222
        else:
223
            logger.error("Cannot announce Glances server on the network: zeroconf library not found.")
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...
224
225
    @staticmethod
226
    def find_active_ip_address():
227
        """Try to find the active IP addresses."""
228
        import netifaces
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
Unable to import 'netifaces'
Loading history...
229
        # Interface of the default gateway
230
        gateway_itf = netifaces.gateways()['default'][netifaces.AF_INET][1]
231
        # IP address for the interface
232
        return netifaces.ifaddresses(gateway_itf)[netifaces.AF_INET][0]['addr']
233
234
    def close(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...
235
        if zeroconf_tag:
236
            self.zeroconf.unregister_service(self.info)
237
            self.zeroconf.close()
238