glances_server.py 7.8 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 20
#
# 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/>.

# Import system libs
A
Alessio Sergi 已提交
21 22 23
import json
import socket
import sys
A
Alessio Sergi 已提交
24
from base64 import b64decode
A
Alessio Sergi 已提交
25 26 27 28 29 30
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 已提交
31

N
Nicolas Hennion 已提交
32
# Import Glances libs
33 34 35
from glances.core.glances_globals import __version__
from glances.core.glances_stats import GlancesStatsServer
from glances.core.glances_timer import Timer
N
Nicolas Hennion 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80


class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler):
    """
    Main XMLRPC handler
    """
    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'
            #    Encoded portion of the header is a string
            #    Need to convert to bytestring
            encodedByteString = encoded.encode()
            #    Decode Base64 byte String to a decoded Byte String
            decodedBytes = b64decode(encodedByteString)
            #    Convert from byte string to a regular String
            decodedString = decodedBytes.decode()
            #    Get the username and password from the string
            (username, _, password) = decodedString.partition(':')
            #    Check that username and password match internal global dictionary
            return self.check_user(username, password)

    def check_user(self, username, password):
        # Check username and password in the dictionnary
        if username in self.server.user_dict:
N
Nicolargo 已提交
81 82 83 84 85 86 87
            from glances.core.glances_password import glancesPassword

            pwd = glancesPassword()

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

    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):
    """
    Init a SimpleXMLRPCServer instance (IPv6-ready)
    """

    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:
A
Alessio Sergi 已提交
115
            print(_("Error: Couldn't open socket: {0}").format(e))
N
Nicolas Hennion 已提交
116 117 118 119 120 121 122 123 124 125 126
            sys.exit(1)

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


class GlancesInstance():
    """
    All the methods of this class are published as XML RPC methods
    """

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

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

N
Nicolas Hennion 已提交
134 135 136 137 138 139 140 141 142
        # 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 已提交
143
            self.stats.update()
N
Nicolas Hennion 已提交
144 145 146 147 148 149 150 151 152
            self.timer = Timer(self.cached_time)

    def init(self):
        # Return the Glances version
        return __version__

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

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

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

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

N
Nicolas Hennion 已提交
168 169
    def __getattr__(self, item):
        """
A
Alessio Sergi 已提交
170
        Overwrite the getattr in case of attribute is not found
N
Nicolas Hennion 已提交
171 172
        The goal is to dynamicaly generate the API get'Stats'() methods
        """
A
Alessio Sergi 已提交
173

174
        # print "DEBUG: Call method: %s" % item
N
Nicolas Hennion 已提交
175 176
        header = 'get'
        # Check if the attribute starts with 'get'
177
        if item.startswith(header):
N
Nicolas Hennion 已提交
178
            try:
179 180
                # Update the stat
                # !!! All the stat are updated before one grab (not optimized)
N
Nicolas Hennion 已提交
181 182 183
                self.stats.update()
                # Return the attribute
                return getattr(self.stats, item)
A
Alessio Sergi 已提交
184
            except Exception:
N
Nicolas Hennion 已提交
185 186 187 188 189 190
                # The method is not found for the plugin
                raise AttributeError(item)
        else:
            # Default behavior
            raise AttributeError(item)

A
Alessio Sergi 已提交
191

N
Nicolas Hennion 已提交
192 193
class GlancesServer():
    """
194
    This class creates and manages the TCP server
N
Nicolas Hennion 已提交
195 196
    """

N
Nicolas Hennion 已提交
197
    def __init__(self, requestHandler=GlancesXMLRPCHandler,
198
                 cached_time=1,
N
Nicolas Hennion 已提交
199 200
                 config=None,
                 args=None):
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 205
        except Exception as e:
            print(_("Error: 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

    def add_user(self, username, password):
        """
        Add an user to the dictionnary
        """
N
Nicolargo 已提交
222
        self.server.user_dict[username] = password
N
Nicolas Hennion 已提交
223 224 225
        self.server.isAuth = True

    def serve_forever(self):
226 227 228
        """
        Call the main loop
        """
N
Nicolas Hennion 已提交
229 230 231 232
        self.server.serve_forever()

    def server_close(self):
        self.server.server_close()
233 234 235 236 237 238

    def end(self):
        """
        End of the Glances server session
        """
        self.server_close()