2014-01-19 02:01:57 +04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2014-04-15 03:05:15 +04:00
|
|
|
# This file is part of Glances.
|
2014-01-19 02:01:57 +04:00
|
|
|
#
|
2016-07-15 18:39:43 +03:00
|
|
|
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
|
2014-01-19 02:01:57 +04:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
2014-06-13 21:00:00 +04:00
|
|
|
"""Manage the Glances server."""
|
|
|
|
|
2014-04-17 19:04:04 +04:00
|
|
|
import json
|
|
|
|
import socket
|
|
|
|
import sys
|
2014-04-22 18:22:22 +04:00
|
|
|
from base64 import b64decode
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2016-03-24 13:33:31 +03:00
|
|
|
from glances import __version__
|
2015-11-14 17:15:15 +03:00
|
|
|
from glances.compat import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
|
|
|
|
from glances.autodiscover import GlancesAutoDiscoverClient
|
|
|
|
from glances.logger import logger
|
2016-03-20 17:03:02 +03:00
|
|
|
from glances.stats_server import GlancesStatsServer
|
2015-11-14 17:15:15 +03:00
|
|
|
from glances.timer import Timer
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
|
2015-11-09 11:51:57 +03:00
|
|
|
class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler, object):
|
2014-06-13 21:00:00 +04:00
|
|
|
|
|
|
|
"""Main XML-RPC handler."""
|
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
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()
|
2015-11-04 21:55:55 +03:00
|
|
|
super(GlancesXMLRPCHandler, self).end_headers()
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
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'
|
2014-06-13 21:00:00 +04:00
|
|
|
# Encoded portion of the header is a string
|
|
|
|
# Need to convert to bytestring
|
2014-06-09 21:19:19 +04:00
|
|
|
encoded_byte_string = encoded.encode()
|
2014-06-13 21:00:00 +04:00
|
|
|
# Decode base64 byte string to a decoded byte string
|
2014-06-09 21:19:19 +04:00
|
|
|
decoded_bytes = b64decode(encoded_byte_string)
|
2014-06-13 21:00:00 +04:00
|
|
|
# Convert from byte string to a regular string
|
2014-06-09 21:19:19 +04:00
|
|
|
decoded_string = decoded_bytes.decode()
|
2014-06-13 21:00:00 +04:00
|
|
|
# Get the username and password from the string
|
2014-06-09 21:19:19 +04:00
|
|
|
(username, _, password) = decoded_string.partition(':')
|
2014-06-13 21:00:00 +04:00
|
|
|
# Check that username and password match internal global dictionary
|
2014-01-19 02:01:57 +04:00
|
|
|
return self.check_user(username, password)
|
|
|
|
|
|
|
|
def check_user(self, username, password):
|
2014-06-13 21:00:00 +04:00
|
|
|
# Check username and password in the dictionary
|
2014-01-19 02:01:57 +04:00
|
|
|
if username in self.server.user_dict:
|
2015-11-14 17:15:15 +03:00
|
|
|
from glances.password import GlancesPassword
|
2014-06-09 21:19:19 +04:00
|
|
|
pwd = GlancesPassword()
|
2014-05-20 22:18:44 +04:00
|
|
|
return pwd.check_password(self.server.user_dict[username], password)
|
|
|
|
else:
|
|
|
|
return False
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2015-02-23 21:39:19 +03:00
|
|
|
def log_message(self, log_format, *args):
|
2014-01-19 02:01:57 +04:00
|
|
|
# No message displayed on the server side
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-11-09 11:51:57 +03:00
|
|
|
class GlancesXMLRPCServer(SimpleXMLRPCServer, object):
|
2014-06-13 21:00:00 +04:00
|
|
|
|
|
|
|
"""Init a SimpleXMLRPCServer instance (IPv6-ready)."""
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
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:
|
2016-06-02 21:02:26 +03:00
|
|
|
logger.error("Couldn't open socket: {}".format(e))
|
2014-01-19 02:01:57 +04:00
|
|
|
sys.exit(1)
|
|
|
|
|
2015-11-04 21:55:55 +03:00
|
|
|
super(GlancesXMLRPCServer, self).__init__((bind_address, bind_port), requestHandler)
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
|
2014-06-09 21:19:19 +04:00
|
|
|
class GlancesInstance(object):
|
2014-06-13 21:00:00 +04:00
|
|
|
|
|
|
|
"""All the methods of this class are published as XML-RPC methods."""
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2014-01-29 21:39:58 +04:00
|
|
|
def __init__(self, cached_time=1, config=None):
|
2014-01-20 01:20:09 +04:00
|
|
|
# Init stats
|
2014-03-22 20:57:40 +04:00
|
|
|
self.stats = GlancesStatsServer(config)
|
2014-01-20 01:20:09 +04:00
|
|
|
|
|
|
|
# Initial update
|
2014-03-22 20:57:40 +04:00
|
|
|
self.stats.update()
|
2014-01-20 01:20:09 +04:00
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
# 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():
|
2014-01-20 01:20:09 +04:00
|
|
|
self.stats.update()
|
2014-01-19 02:01:57 +04:00
|
|
|
self.timer = Timer(self.cached_time)
|
|
|
|
|
|
|
|
def init(self):
|
|
|
|
# Return the Glances version
|
2016-03-24 13:33:31 +03:00
|
|
|
return __version__
|
2014-01-19 02:01:57 +04:00
|
|
|
|
|
|
|
def getAll(self):
|
|
|
|
# Update and return all the stats
|
|
|
|
self.__update__()
|
2014-01-20 01:20:09 +04:00
|
|
|
return json.dumps(self.stats.getAll())
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2014-03-19 12:41:53 +04:00
|
|
|
def getAllPlugins(self):
|
|
|
|
# Return the plugins list
|
|
|
|
return json.dumps(self.stats.getAllPlugins())
|
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
def getAllLimits(self):
|
2014-03-28 15:05:55 +04:00
|
|
|
# Return all the plugins limits
|
2015-01-18 20:21:26 +03:00
|
|
|
return json.dumps(self.stats.getAllLimitsAsDict())
|
|
|
|
|
|
|
|
def getAllViews(self):
|
|
|
|
# Return all the plugins views
|
|
|
|
return json.dumps(self.stats.getAllViewsAsDict())
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2014-01-22 13:49:35 +04:00
|
|
|
def __getattr__(self, item):
|
2014-06-13 21:00:00 +04:00
|
|
|
"""Overwrite the getattr method in case of attribute is not found.
|
2014-03-23 21:08:19 +04:00
|
|
|
|
2014-06-13 21:00:00 +04:00
|
|
|
The goal is to dynamically generate the API get'Stats'() methods.
|
|
|
|
"""
|
2014-01-22 13:49:35 +04:00
|
|
|
header = 'get'
|
|
|
|
# Check if the attribute starts with 'get'
|
2014-04-23 14:23:23 +04:00
|
|
|
if item.startswith(header):
|
2014-01-22 13:49:35 +04:00
|
|
|
try:
|
2014-01-27 00:42:48 +04:00
|
|
|
# Update the stat
|
2014-10-28 10:55:25 +03:00
|
|
|
self.__update__()
|
2014-01-22 13:49:35 +04:00
|
|
|
# Return the attribute
|
|
|
|
return getattr(self.stats, item)
|
2014-03-23 21:08:19 +04:00
|
|
|
except Exception:
|
2014-01-22 13:49:35 +04:00
|
|
|
# The method is not found for the plugin
|
|
|
|
raise AttributeError(item)
|
|
|
|
else:
|
|
|
|
# Default behavior
|
|
|
|
raise AttributeError(item)
|
|
|
|
|
2014-03-23 21:08:19 +04:00
|
|
|
|
2014-06-09 21:19:19 +04:00
|
|
|
class GlancesServer(object):
|
2014-06-13 21:00:00 +04:00
|
|
|
|
|
|
|
"""This class creates and manages the TCP server."""
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2014-04-13 12:34:07 +04:00
|
|
|
def __init__(self, requestHandler=GlancesXMLRPCHandler,
|
2014-01-29 21:39:58 +04:00
|
|
|
cached_time=1,
|
2014-04-13 12:34:07 +04:00
|
|
|
config=None,
|
|
|
|
args=None):
|
2014-10-27 16:36:24 +03:00
|
|
|
# Args
|
|
|
|
self.args = args
|
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
# Init the XML RPC server
|
|
|
|
try:
|
2014-04-22 18:22:22 +04:00
|
|
|
self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler)
|
2014-05-30 00:22:22 +04:00
|
|
|
except Exception as e:
|
2016-06-02 21:02:26 +03:00
|
|
|
logger.critical("Cannot start Glances server: {}".format(e))
|
2014-01-19 02:01:57 +04:00
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
# The users dict
|
2014-05-20 22:18:44 +04:00
|
|
|
# username / password couple
|
2014-01-19 02:01:57 +04:00
|
|
|
# By default, no auth is needed
|
|
|
|
self.server.user_dict = {}
|
|
|
|
self.server.isAuth = False
|
2014-01-20 01:20:09 +04:00
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
# Register functions
|
|
|
|
self.server.register_introspection_functions()
|
2014-01-29 21:39:58 +04:00
|
|
|
self.server.register_instance(GlancesInstance(cached_time, config))
|
2014-01-19 02:01:57 +04:00
|
|
|
|
2014-10-27 16:36:24 +03:00
|
|
|
if not self.args.disable_autodiscover:
|
|
|
|
# Note: The Zeroconf service name will be based on the hostname
|
2016-07-15 18:39:43 +03:00
|
|
|
# Correct issue: Zeroconf problem with zeroconf service name #889
|
|
|
|
self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname().split('.', 1)[0], args)
|
2014-10-27 16:36:24 +03:00
|
|
|
else:
|
|
|
|
logger.info("Glances autodiscover announce is disabled")
|
|
|
|
|
2014-01-19 02:01:57 +04:00
|
|
|
def add_user(self, username, password):
|
2014-06-13 21:00:00 +04:00
|
|
|
"""Add an user to the dictionary."""
|
2014-05-20 22:18:44 +04:00
|
|
|
self.server.user_dict[username] = password
|
2014-01-19 02:01:57 +04:00
|
|
|
self.server.isAuth = True
|
|
|
|
|
|
|
|
def serve_forever(self):
|
2014-06-13 21:00:00 +04:00
|
|
|
"""Call the main loop."""
|
2014-01-19 02:01:57 +04:00
|
|
|
self.server.serve_forever()
|
|
|
|
|
|
|
|
def server_close(self):
|
2014-06-13 21:00:00 +04:00
|
|
|
"""Close the Glances server session."""
|
2014-01-19 02:01:57 +04:00
|
|
|
self.server.server_close()
|
2014-03-28 11:46:55 +04:00
|
|
|
|
|
|
|
def end(self):
|
2014-06-13 21:00:00 +04:00
|
|
|
"""End of the Glances server session."""
|
2014-10-27 16:36:24 +03:00
|
|
|
if not self.args.disable_autodiscover:
|
|
|
|
self.autodiscover_client.close()
|
2014-03-28 11:46:55 +04:00
|
|
|
self.server_close()
|