glances_server.py 8.2 KB
Newer Older
N
Nicolas Hennion 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
N
Nicolas Hennion 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# 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/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""Manage the Glances server."""

N
Nicolas Hennion 已提交
22
# Import system libs
A
Alessio Sergi 已提交
23 24 25
import json
import socket
import sys
A
Alessio Sergi 已提交
26
from base64 import b64decode
A
Alessio Sergi 已提交
27 28 29 30 31 32
try:
    from xmlrpc.server import SimpleXMLRPCRequestHandler
    from xmlrpc.server import SimpleXMLRPCServer
except ImportError:  # Python 2
    from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
    from SimpleXMLRPCServer import SimpleXMLRPCServer
N
Nicolas Hennion 已提交
33

N
Nicolas Hennion 已提交
34
# Import Glances libs
35
from glances.core.glances_globals import logger, version
36 37
from glances.core.glances_stats import GlancesStatsServer
from glances.core.glances_timer import Timer
38
from glances.core.glances_autodiscover import GlancesAutoDiscoverClient
N
Nicolas Hennion 已提交
39 40 41


class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler):
A
PEP 257  
Alessio Sergi 已提交
42 43 44

    """Main XML-RPC handler."""

N
Nicolas Hennion 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    rpc_paths = ('/RPC2', )

    def end_headers(self):
        # Hack to add a specific header
        # Thk to: https://gist.github.com/rca/4063325
        self.send_my_headers()
        SimpleXMLRPCRequestHandler.end_headers(self)

    def send_my_headers(self):
        # Specific header is here (solved the issue #227)
        self.send_header("Access-Control-Allow-Origin", "*")

    def authenticate(self, headers):
        # auth = headers.get('Authorization')
        try:
            (basic, _, encoded) = headers.get('Authorization').partition(' ')
        except Exception:
            # Client did not ask for authentidaction
            # If server need it then exit
            return not self.server.isAuth
        else:
            # Client authentication
            (basic, _, encoded) = headers.get('Authorization').partition(' ')
            assert basic == 'Basic', 'Only basic authentication supported'
A
PEP 257  
Alessio Sergi 已提交
69 70
            # Encoded portion of the header is a string
            # Need to convert to bytestring
A
Alessio Sergi 已提交
71
            encoded_byte_string = encoded.encode()
A
PEP 257  
Alessio Sergi 已提交
72
            # Decode base64 byte string to a decoded byte string
A
Alessio Sergi 已提交
73
            decoded_bytes = b64decode(encoded_byte_string)
A
PEP 257  
Alessio Sergi 已提交
74
            # Convert from byte string to a regular string
A
Alessio Sergi 已提交
75
            decoded_string = decoded_bytes.decode()
A
PEP 257  
Alessio Sergi 已提交
76
            # Get the username and password from the string
A
Alessio Sergi 已提交
77
            (username, _, password) = decoded_string.partition(':')
A
PEP 257  
Alessio Sergi 已提交
78
            # Check that username and password match internal global dictionary
N
Nicolas Hennion 已提交
79 80 81
            return self.check_user(username, password)

    def check_user(self, username, password):
A
PEP 257  
Alessio Sergi 已提交
82
        # Check username and password in the dictionary
N
Nicolas Hennion 已提交
83
        if username in self.server.user_dict:
A
Alessio Sergi 已提交
84
            from glances.core.glances_password import GlancesPassword
N
Nicolargo 已提交
85

A
Alessio Sergi 已提交
86
            pwd = GlancesPassword()
N
Nicolargo 已提交
87 88 89 90

            return pwd.check_password(self.server.user_dict[username], password)
        else:
            return False
N
Nicolas Hennion 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

    def parse_request(self):
        if SimpleXMLRPCRequestHandler.parse_request(self):
            # Next we authenticate
            if self.authenticate(self.headers):
                return True
            else:
                # if authentication fails, tell the client
                self.send_error(401, 'Authentication failed')
        return False

    def log_message(self, format, *args):
        # No message displayed on the server side
        pass


class GlancesXMLRPCServer(SimpleXMLRPCServer):
A
PEP 257  
Alessio Sergi 已提交
108 109

    """Init a SimpleXMLRPCServer instance (IPv6-ready)."""
N
Nicolas Hennion 已提交
110 111 112 113 114 115 116

    def __init__(self, bind_address, bind_port=61209,
                 requestHandler=GlancesXMLRPCHandler):

        try:
            self.address_family = socket.getaddrinfo(bind_address, bind_port)[0][0]
        except socket.error as e:
N
Nicolas Hennion 已提交
117
            logger.error(_("Couldn't open socket: {0}").format(e))
N
Nicolas Hennion 已提交
118 119 120 121 122 123
            sys.exit(1)

        SimpleXMLRPCServer.__init__(self, (bind_address, bind_port),
                                    requestHandler)


A
Alessio Sergi 已提交
124
class GlancesInstance(object):
A
PEP 257  
Alessio Sergi 已提交
125 126

    """All the methods of this class are published as XML-RPC methods."""
N
Nicolas Hennion 已提交
127

128
    def __init__(self, cached_time=1, config=None):
N
Nicolas Hennion 已提交
129
        # Init stats
130
        self.stats = GlancesStatsServer(config)
N
Nicolas Hennion 已提交
131 132

        # Initial update
133
        self.stats.update()
N
Nicolas Hennion 已提交
134

N
Nicolas Hennion 已提交
135 136 137 138 139 140 141 142 143
        # cached_time is the minimum time interval between stats updates
        # i.e. XML/RPC calls will not retrieve updated info until the time
        # since last update is passed (will retrieve old cached info instead)
        self.timer = Timer(0)
        self.cached_time = cached_time

    def __update__(self):
        # Never update more than 1 time per cached_time
        if self.timer.finished():
N
Nicolas Hennion 已提交
144
            self.stats.update()
N
Nicolas Hennion 已提交
145 146 147 148
            self.timer = Timer(self.cached_time)

    def init(self):
        # Return the Glances version
A
Alessio Sergi 已提交
149
        return version
N
Nicolas Hennion 已提交
150 151 152 153

    def getAll(self):
        # Update and return all the stats
        self.__update__()
N
Nicolas Hennion 已提交
154
        return json.dumps(self.stats.getAll())
N
Nicolas Hennion 已提交
155

156 157 158 159
    def getAllPlugins(self):
        # Return the plugins list
        return json.dumps(self.stats.getAllPlugins())

N
Nicolas Hennion 已提交
160
    def getAllLimits(self):
161 162
        # Return all the plugins limits
        return json.dumps(self.stats.getAllLimits())
N
Nicolas Hennion 已提交
163 164 165

    def getAllMonitored(self):
        # Return the processes monitored list
166 167
        # return json.dumps(self.monitors.getAll())
        return json.dumps(self.stats.getAll()['monitor'])
N
Nicolas Hennion 已提交
168

N
Nicolas Hennion 已提交
169
    def __getattr__(self, item):
A
PEP 257  
Alessio Sergi 已提交
170
        """Overwrite the getattr method in case of attribute is not found.
A
Alessio Sergi 已提交
171

A
PEP 257  
Alessio Sergi 已提交
172 173
        The goal is to dynamically generate the API get'Stats'() methods.
        """
N
Nicolas Hennion 已提交
174 175
        header = 'get'
        # Check if the attribute starts with 'get'
176
        if item.startswith(header):
N
Nicolas Hennion 已提交
177
            try:
178
                # Update the stat
N
Nicolas Hennion 已提交
179
                self.__update__()
N
Nicolas Hennion 已提交
180 181
                # Return the attribute
                return getattr(self.stats, item)
A
Alessio Sergi 已提交
182
            except Exception:
N
Nicolas Hennion 已提交
183 184 185 186 187 188
                # The method is not found for the plugin
                raise AttributeError(item)
        else:
            # Default behavior
            raise AttributeError(item)

A
Alessio Sergi 已提交
189

A
Alessio Sergi 已提交
190
class GlancesServer(object):
A
PEP 257  
Alessio Sergi 已提交
191 192

    """This class creates and manages the TCP server."""
N
Nicolas Hennion 已提交
193

N
Nicolas Hennion 已提交
194
    def __init__(self, requestHandler=GlancesXMLRPCHandler,
195
                 cached_time=1,
N
Nicolas Hennion 已提交
196 197
                 config=None,
                 args=None):
198 199 200
        # Args
        self.args = args

N
Nicolas Hennion 已提交
201 202
        # Init the XML RPC server
        try:
A
Alessio Sergi 已提交
203
            self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler)
A
Alessio Sergi 已提交
204
        except Exception as e:
205
            logger.critical(_("Cannot start Glances server: {0}").format(e))
N
Nicolas Hennion 已提交
206 207 208
            sys.exit(2)

        # The users dict
N
Nicolargo 已提交
209
        # username / password couple
N
Nicolas Hennion 已提交
210 211 212
        # By default, no auth is needed
        self.server.user_dict = {}
        self.server.isAuth = False
N
Nicolas Hennion 已提交
213

N
Nicolas Hennion 已提交
214 215
        # Register functions
        self.server.register_introspection_functions()
216
        self.server.register_instance(GlancesInstance(cached_time, config))
N
Nicolas Hennion 已提交
217

218 219 220 221 222 223
        if not self.args.disable_autodiscover:
            # Note: The Zeroconf service name will be based on the hostname
            self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname(), args)
        else:
            logger.info("Glances autodiscover announce is disabled")

N
Nicolas Hennion 已提交
224
    def add_user(self, username, password):
A
PEP 257  
Alessio Sergi 已提交
225
        """Add an user to the dictionary."""
N
Nicolargo 已提交
226
        self.server.user_dict[username] = password
N
Nicolas Hennion 已提交
227 228 229
        self.server.isAuth = True

    def serve_forever(self):
A
PEP 257  
Alessio Sergi 已提交
230
        """Call the main loop."""
N
Nicolas Hennion 已提交
231 232 233
        self.server.serve_forever()

    def server_close(self):
A
PEP 257  
Alessio Sergi 已提交
234
        """Close the Glances server session."""
N
Nicolas Hennion 已提交
235
        self.server.server_close()
236 237

    def end(self):
A
PEP 257  
Alessio Sergi 已提交
238
        """End of the Glances server session."""
239 240
        if not self.args.disable_autodiscover:
            self.autodiscover_client.close()
241
        self.server_close()