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 |
||
23 | import sys |
||
24 | |||
25 | from glances.globals import BSD |
||
26 | from glances.logger import logger |
||
27 | |||
28 | try: |
||
29 | from zeroconf import ( |
||
30 | __version__ as __zeroconf_version, |
||
31 | ServiceBrowser, |
||
32 | ServiceInfo, |
||
33 | Zeroconf |
||
34 | ) |
||
35 | zeroconf_tag = True |
||
0 ignored issues
–
show
|
|||
36 | except ImportError: |
||
37 | zeroconf_tag = False |
||
0 ignored issues
–
show
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
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
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)) |
||
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
|
|||
50 | # Correct issue: zeroconf problem with zeroconf_type = "_%s._tcp." % 'glances' #888 |
||
0 ignored issues
–
show
|
|||
51 | zeroconf_type = "_%s._tcp.local." % 'glances' |
||
0 ignored issues
–
show
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
|
|||
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
|
|||
69 | self._server_list[server_pos][key] = value |
||
70 | |||
71 | def add_server(self, name, ip, port): |
||
0 ignored issues
–
show
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
|
|||
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
|
|||
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
|
|||
93 | logger.debug("Updated servers list (%s servers): %s" % ( |
||
0 ignored issues
–
show
|
|||
94 | len(self._server_list), self._server_list)) |
||
95 | except ValueError: |
||
96 | logger.error( |
||
0 ignored issues
–
show
|
|||
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
|
|||
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
|
|||
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
|
|||
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
|
|||
138 | return True |
||
139 | |||
140 | def remove_service(self, zeroconf, srv_type, srv_name): |
||
0 ignored issues
–
show
|
|||
141 | """Remove the server from the list.""" |
||
142 | self.servers.remove_server(srv_name) |
||
143 | logger.info( |
||
0 ignored issues
–
show
|
|||
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
|
|||
150 | |||
151 | def __init__(self, args=None): |
||
0 ignored issues
–
show
|
|||
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
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
|
|||
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
|
|||
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: |
||
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
|
|||
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
|
|||
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
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)) |
||
196 | |||
197 | # XXX *BSDs: Segmentation fault (core dumped) |
||
0 ignored issues
–
show
|
|||
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
|
|||
210 | |||
211 | # Start the zeroconf service |
||
212 | self.info = ServiceInfo( |
||
213 | zeroconf_type, '{}:{}.{}'.format(hostname, args.port, zeroconf_type), |
||
0 ignored issues
–
show
|
|||
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
|
|||
216 | try: |
||
217 | self.zeroconf.register_service(self.info) |
||
218 | except socket.error as e: |
||
0 ignored issues
–
show
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
|
|||
220 | else: |
||
221 | print("Announce the Glances server on the LAN (using {} IP address)".format(zeroconf_bind_address)) |
||
0 ignored issues
–
show
|
|||
222 | else: |
||
223 | logger.error("Cannot announce Glances server on the network: zeroconf library not found.") |
||
0 ignored issues
–
show
|
|||
224 | |||
225 | @staticmethod |
||
226 | def find_active_ip_address(): |
||
227 | """Try to find the active IP addresses.""" |
||
228 | import netifaces |
||
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 |
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.