glances_autodiscover.py 9.2 KB
Newer Older
N
Nicolargo 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2014 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Manage autodiscover Glances server (thk to the ZeroConf protocol)."""

# Import system libs
import socket
N
Nicolargo 已提交
24 25 26 27 28
try:
    import netifaces
    netifaces_tag = True
except ImportError:
    netifaces_tag = True
N
Nicolargo 已提交
29 30 31 32 33 34 35
try:
    from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
    zeroconf_tag = True
except ImportError:
    zeroconf_tag = False

# Import Glances libs
N
Nicolargo 已提交
36
from glances.core.glances_globals import appname, logger
N
Nicolargo 已提交
37

N
Nicolargo 已提交
38 39 40 41 42 43 44 45 46
# Need Zeroconf 0.16 or higher
try:
    from zeroconf import __version__ as __zeroconf_version__
    zeroconf_version_t = tuple([int(i) for i in __zeroconf_version__.split('.')])
    logger.debug("Zeroconf Python lib %s detected" % __zeroconf_version__)
    if (zeroconf_version_t[0] == 0) and (zeroconf_version_t[1] < 16):
        logger.warning("Please install Zeroconf Python lib 0.16 or higher")
except ImportError:
    pass
47

N
Nicolargo 已提交
48 49 50 51 52
# Global var
zeroconf_type = "_%s._tcp." % appname


class AutoDiscovered(object):
N
Nicolargo 已提交
53

N
Nicolargo 已提交
54 55 56
    """Class to manage the auto discovered servers dict"""

    def __init__(self):
57 58 59
        # server_dict is a list of dict (JSON compliant)
        # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ]
        self._server_list = []
N
Nicolargo 已提交
60 61

    def get_servers_list(self):
62 63
        """Return the current server list (list of dict)"""
        return self._server_list
N
Nicolargo 已提交
64

65 66 67 68
    def set_server(self, server_pos, key, value):
        """Set the key to the value for the server_pos (position in the list)"""
        self._server_list[server_pos][key] = value

N
Nicolargo 已提交
69
    def add_server(self, name, ip, port):
70
        """Add a new server to the list"""
N
Nicolargo 已提交
71 72 73 74
        new_server = {'key': name,  # Zeroconf name with both hostname and port
                      'name': name.split(':')[0],  # Short name
                      'ip': ip,  # IP address seen by the client
                      'port': port,  # TCP port
N
Nicolargo 已提交
75 76 77 78
                      'username': 'glances',  # Default username
                      'password': '',  # Default password
                      'status': 'UNKNOWN',  # Server status: 'UNKNOWN', 'OFFLINE', 'ONLINE', 'PROTECTED'
                      'type': 'DYNAMIC',  # Server type: 'STATIC' or 'DYNAMIC'
N
Nicolargo 已提交
79
                      }
80
        self._server_list.append(new_server)
N
Nicolargo 已提交
81 82
        logger.debug("Updated servers list (%s servers): %s" %
                     (len(self._server_list), self._server_list))
N
Nicolargo 已提交
83 84 85

    def remove_server(self, name):
        """Remove a server from the dict"""
86 87 88 89 90
        for i in self._server_list:
            if i['key'] == name:
                try:
                    self._server_list.remove(i)
                    logger.debug("Remove server %s from the list" % name)
N
Nicolargo 已提交
91 92
                    logger.debug("Updated servers list (%s servers): %s" % (
                        len(self._server_list), self._server_list))
93
                except ValueError:
N
Nicolargo 已提交
94 95
                    logger.error(
                        "Can not remove server %s from the list" % name)
N
Nicolargo 已提交
96 97 98


class GlancesAutoDiscoverListener(object):
N
Nicolargo 已提交
99

N
Nicolargo 已提交
100 101 102 103 104 105 106
    """Zeroconf listener for Glances server"""

    def __init__(self):
        # Create an instance of the servers list
        self.servers = AutoDiscovered()

    def get_servers_list(self):
107
        """Return the current server list (list of dict)"""
N
Nicolargo 已提交
108 109
        return self.servers.get_servers_list()

110 111 112 113
    def set_server(self, server_pos, key, value):
        """Set the key to the value for the server_pos (position in the list)"""
        self.servers.set_server(server_pos, key, value)

114
    def add_service(self, zeroconf, srv_type, srv_name):
N
Nicolargo 已提交
115 116 117 118 119 120
        """Method called when a new Zeroconf client is detected
        Return True if the zeroconf client is a Glances server
        Note: the return code will never be used
        """
        if srv_type != zeroconf_type:
            return False
N
Nicolargo 已提交
121 122
        logger.debug("Check new Zeroconf server: %s / %s" %
                     (srv_type, srv_name))
123
        info = zeroconf.get_service_info(srv_type, srv_name)
N
Nicolargo 已提交
124
        if info:
125 126
            new_server_ip = socket.inet_ntoa(info.address)
            new_server_port = info.port
N
Nicolargo 已提交
127 128 129

            # Add server to the global dict
            self.servers.add_server(srv_name, new_server_ip, new_server_port)
N
Nicolargo 已提交
130 131
            logger.info("New Glances server detected (%s from %s:%s)" %
                        (srv_name, new_server_ip, new_server_port))
N
Nicolargo 已提交
132
        else:
N
Nicolargo 已提交
133 134
            logger.warning(
                "New Glances server detected, but Zeroconf info failed to be grabbed")
N
Nicolargo 已提交
135 136
        return True

137
    def remove_service(self, zeroconf, srv_type, srv_name):
N
Nicolargo 已提交
138 139
        # Remove the server from the list
        self.servers.remove_server(srv_name)
N
Nicolargo 已提交
140 141
        logger.info(
            "Glances server %s removed from the autodetect list" % srv_name)
N
Nicolargo 已提交
142 143 144


class GlancesAutoDiscoverServer(object):
N
Nicolargo 已提交
145

N
Nicolargo 已提交
146 147 148 149 150
    """Implementation of the Zeroconf protocol (server side for the Glances client)"""

    def __init__(self, args=None):
        if zeroconf_tag:
            logger.info("Init autodiscover mode (Zeroconf protocol)")
151 152
            try:
                self.zeroconf = Zeroconf()
153
            except socket.error as e:
154 155
                logger.error("Can not start Zeroconf (%s)" % e)
                self.zeroconf_enable_tag = False
156
            else:
157 158 159 160
                self.listener = GlancesAutoDiscoverListener()
                self.browser = ServiceBrowser(
                    self.zeroconf, zeroconf_type, self.listener)
                self.zeroconf_enable_tag = True
N
Nicolargo 已提交
161
        else:
162
            logger.error(
N
Nicolargo 已提交
163
                "Can not start autodiscover mode (Zeroconf lib is not installed)")
164
            self.zeroconf_enable_tag = False
N
Nicolargo 已提交
165 166 167

    def get_servers_list(self):
        """Return the current server list (dict of dict)"""
168
        if zeroconf_tag and self.zeroconf_enable_tag:
N
Nicolargo 已提交
169 170
            return self.listener.get_servers_list()
        else:
N
Nicolargo 已提交
171
            return []
N
Nicolargo 已提交
172

173 174
    def set_server(self, server_pos, key, value):
        """Set the key to the value for the server_pos (position in the list)"""
175 176
        if zeroconf_tag and self.zeroconf_enable_tag:
            self.listener.set_server(server_pos, key, value)
177

N
Nicolargo 已提交
178
    def close(self):
179
        if zeroconf_tag and self.zeroconf_enable_tag:
N
Nicolargo 已提交
180 181 182 183
            self.zeroconf.close()


class GlancesAutoDiscoverClient(object):
N
Nicolargo 已提交
184

N
Nicolargo 已提交
185 186 187
    """Implementation of the Zeroconf protocol (client side for the Glances server)"""

    def __init__(self, hostname, args=None):
N
Nicolargo 已提交
188
        if netifaces_tag:
189 190
            # -B @ overwrite the dynamic IPv4 choice
            if args.bind_address != '0.0.0.0':
N
Nicolargo 已提交
191
                zeroconf_bind_address = args.bind_address
192 193
            else:
                zeroconf_bind_address = self.find_active_ip_address()
N
Nicolargo 已提交
194 195
            print("Announce the Glances server on the local area network (using %s IP address)" %
                  zeroconf_bind_address)
N
Nicolargo 已提交
196 197 198 199

            if zeroconf_tag:
                logger.info(
                    "Announce the Glances server on the local area network (using %s IP address)" % zeroconf_bind_address)
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
                try:
                    self.zeroconf = Zeroconf()
                except socket.error as e:
                    logger.error("Can not start Zeroconf (%s)" % e)
                else:
                    self.info = ServiceInfo(zeroconf_type,
                                            hostname + ':' +
                                            str(args.port) + '.' + zeroconf_type,
                                            address=socket.inet_aton(
                                                zeroconf_bind_address),
                                            port=args.port,
                                            weight=0,
                                            priority=0,
                                            properties={},
                                            server=hostname)
215
                    self.zeroconf.register_service(self.info)
N
Nicolargo 已提交
216 217 218
            else:
                logger.error(
                    "Can not announce Glances server on the network (Zeroconf lib is not installed)")
N
Nicolargo 已提交
219
        else:
N
Nicolargo 已提交
220 221
            logger.error(
                "Can not announce Glances server on the network (Netifaces lib is not installed)")
N
Nicolargo 已提交
222

223 224 225 226 227 228 229
    def find_active_ip_address(self):
        """Try to find the active IP addresses"""
        try:
            # Interface of the default gatewau
            gateway_itf = netifaces.gateways()['default'][netifaces.AF_INET][1]
            # IP address for the interface
            return netifaces.ifaddresses(gateway_itf)[netifaces.AF_INET][0]['addr']
A
Alessio Sergi 已提交
230
        except Exception:
231 232
            return None

N
Nicolargo 已提交
233 234
    def close(self):
        if zeroconf_tag:
235
            self.zeroconf.unregister_service(self.info)
N
Nicolargo 已提交
236
            self.zeroconf.close()