[X2Go-Commits] [x2gobroker] 05/08: New feature: x2gobroker-loadchecker daemon.
git-admin at x2go.org
git-admin at x2go.org
Thu Mar 26 11:47:54 CET 2015
This is an automated email from the git hooks/post-receive script.
x2go pushed a commit to branch master
in repository x2gobroker.
commit c571e891e4203eedd1b4f68346e66eb49b09c7b5
Author: Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
Date: Thu Mar 26 09:57:39 2015 +0100
New feature: x2gobroker-loadchecker daemon.
---
bin/x2gobroker-ssh | Bin 10328 -> 10328 bytes
debian/control | 33 ++
debian/x2gobroker-agent.postrm | 6 +-
debian/x2gobroker-authservice.postrm | 6 +-
debian/x2gobroker-daemon.postrm | 6 +-
debian/x2gobroker-loadchecker.default | 1 +
debian/x2gobroker-loadchecker.init | 1 +
debian/x2gobroker-loadchecker.install | 3 +
debian/x2gobroker-loadchecker.manpages | 1 +
debian/x2gobroker-loadchecker.postinst | 76 +++++
...ervice.postrm => x2gobroker-loadchecker.postrm} | 8 +-
debian/x2gobroker-loadchecker.service | 1 +
debian/x2gobroker-wsgi.postrm | 6 +-
defaults/python-x2gobroker.default | 3 +
defaults/x2gobroker-loadchecker.default | 20 ++
etc/broker/defaults.conf | 3 +
etc/broker/x2gobroker-loadchecker-logger.conf | 51 ++++
etc/x2gobroker.conf | 49 +++
init/x2gobroker-loadchecker.init | 104 +++++++
lib/x2gobroker-agent.pl | 49 +++
logrotate/x2gobroker-loadchecker | 14 +
man/man8/x2gobroker-authservice.8 | 2 +-
man/man8/x2gobroker-loadchecker.8 | 93 ++++++
sbin/x2gobroker-loadchecker | 319 ++++++++++++++++++++
x2gobroker-loadchecker.service | 12 +
x2gobroker.spec | 119 ++++++++
x2gobroker/agent.py | 25 ++
x2gobroker/brokers/base_broker.py | 115 +++++++
x2gobroker/defaults.py | 18 ++
x2gobroker/loadchecker.py | 219 ++++++++++++++
30 files changed, 1346 insertions(+), 17 deletions(-)
diff --git a/bin/x2gobroker-ssh b/bin/x2gobroker-ssh
index 10f24a3..ad1a492 100755
Binary files a/bin/x2gobroker-ssh and b/bin/x2gobroker-ssh differ
diff --git a/debian/control b/debian/control
index aca8fce..1a90ed5 100644
--- a/debian/control
+++ b/debian/control
@@ -115,6 +115,37 @@ Description: X2Go Session Broker (PAM authentication service)
This package contains the authentication service
against the PAM system.
+Package: x2gobroker-loadchecker
+Architecture: all
+Depends:
+ ${python:Depends},
+ ${misc:Depends},
+ adduser,
+ python,
+ python-argparse,
+ python-setproctitle,
+ python-x2gobroker (>= ${source:Version}), python-x2gobroker (<< ${source:Version}.1~),
+Suggests:
+ x2gobroker-daemon,
+Description: X2Go Session Broker (load checker service)
+ X2Go is a server based computing environment with
+ - session resuming
+ - low bandwidth support
+ - session brokerage support
+ - client side mass storage mounting support
+ - client side printing support
+ - audio support
+ - authentication by smartcard and USB stick
+ .
+ The session broker is a server tool for X2Go that tells your X2Go Client
+ application in a terminal server cluster what servers and session types are
+ most appropriate for the user in front of the X2Go terminal.
+ .
+ A session broker is most useful in load balanced X2Go server farms.
+ .
+ This package contains the load checker service required for broker setups
+ with dynamic load balancing.
+
Package: x2gobroker-daemon
Architecture: all
Depends:
@@ -123,6 +154,8 @@ Depends:
x2gobroker (>= ${source:Version}), x2gobroker (<< ${source:Version}.1~),
Recommends:
x2gobroker-authservice,
+Suggests:
+ x2gobroker-loadchecker,
Description: X2Go Session Broker (standalone daemon)
X2Go is a server based computing environment with
- session resuming
diff --git a/debian/x2gobroker-agent.postrm b/debian/x2gobroker-agent.postrm
index 0739da6..0f5c579 100755
--- a/debian/x2gobroker-agent.postrm
+++ b/debian/x2gobroker-agent.postrm
@@ -23,15 +23,15 @@ case "$1" in
dpkg-statoverride --remove /usr/lib/x2go/x2gobroker-agent
fi
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
if dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
dpkg-statoverride --remove /var/log/x2gobroker
fi
rm -Rf /var/log/x2gobroker
fi
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
- # remove user/group x2gobroker from system (if not in use by x2gobroker-daemon, x2gobroker-authservice, x2gobroker-wsgi)
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
+ # remove user/group x2gobroker from system (if not in use by x2gobroker-daemon, x2gobroker-authservice, x2gobroker-wsgi, x2gobroker-loadchecker)
getent passwd x2gobroker 1>/dev/null && deluser x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
diff --git a/debian/x2gobroker-authservice.postrm b/debian/x2gobroker-authservice.postrm
index 851bfa5..6db5213 100755
--- a/debian/x2gobroker-authservice.postrm
+++ b/debian/x2gobroker-authservice.postrm
@@ -19,15 +19,15 @@ set -e
case "$1" in
purge)
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-agent ] ; then
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
if dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
dpkg-statoverride --remove /var/log/x2gobroker
fi
rm -Rf /var/log/x2gobroker
fi
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
- # remove user/group x2gobroker from system (only if not in use by x2gobroker-daemon, x2gobroker-agent, x2gobroker-wsgi)
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
+ # remove user/group x2gobroker from system (only if not in use by x2gobroker-daemon, x2gobroker-agent, x2gobroker-wsgi, x2gobroker-loadchecker)
getent passwd x2gobroker 1>/dev/null && deluser x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
diff --git a/debian/x2gobroker-daemon.postrm b/debian/x2gobroker-daemon.postrm
index 5d76a1b..ab50ca0 100755
--- a/debian/x2gobroker-daemon.postrm
+++ b/debian/x2gobroker-daemon.postrm
@@ -19,15 +19,15 @@ set -e
case "$1" in
purge)
- if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
+ if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
if dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
dpkg-statoverride --remove /var/log/x2gobroker
fi
rm -Rf /var/log/x2gobroker
fi
- if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
- # remove user/group x2gobroker from system (only if not in use by x2gobroker-agent, x2gobroker-authservice, x2gobroker-wsgi)
+ if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
+ # remove user/group x2gobroker from system (only if not in use by x2gobroker-agent, x2gobroker-authservice, x2gobroker-wsgi, x2gobroker-loadchecker)
getent passwd x2gobroker 1>/dev/null && deluser x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
diff --git a/debian/x2gobroker-loadchecker.default b/debian/x2gobroker-loadchecker.default
new file mode 120000
index 0000000..25ba295
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.default
@@ -0,0 +1 @@
+../defaults/x2gobroker-loadchecker.default
\ No newline at end of file
diff --git a/debian/x2gobroker-loadchecker.init b/debian/x2gobroker-loadchecker.init
new file mode 120000
index 0000000..b970565
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.init
@@ -0,0 +1 @@
+../init/x2gobroker-loadchecker.init
\ No newline at end of file
diff --git a/debian/x2gobroker-loadchecker.install b/debian/x2gobroker-loadchecker.install
new file mode 100644
index 0000000..769abbc
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.install
@@ -0,0 +1,3 @@
+sbin/x2gobroker-loadchecker usr/sbin/
+logrotate/x2gobroker-loadchecker etc/logrotate.d/
+etc/broker/x2gobroker-loadchecker-logger.conf etc/x2go/broker
\ No newline at end of file
diff --git a/debian/x2gobroker-loadchecker.manpages b/debian/x2gobroker-loadchecker.manpages
new file mode 100644
index 0000000..1832989
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.manpages
@@ -0,0 +1 @@
+man/man8/x2gobroker-loadchecker.8
\ No newline at end of file
diff --git a/debian/x2gobroker-loadchecker.postinst b/debian/x2gobroker-loadchecker.postinst
new file mode 100755
index 0000000..c2f42ce
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.postinst
@@ -0,0 +1,76 @@
+#!/bin/sh
+# postinst script for x2gobroker-loadchecker
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+
+ # setup user/group x2gobroker
+ if ! getent group x2gobroker 1>/dev/null; then
+ echo "Creating x2gobroker group." 1>&2
+ addgroup --system x2gobroker
+ else
+ echo "Group x2gobroker already exists." 1>&2
+ fi
+ if ! getent passwd x2gobroker 1>/dev/null; then
+ echo "Creating x2gobroker user." 1>&2
+ adduser --system \
+ --disabled-password --disabled-login \
+ --shell /bin/bash --group --home /var/lib/x2gobroker x2gobroker
+ else
+ echo "User x2gobroker already exists." 1>&2
+
+ # make sure the home directory exists belongs to x2gobroker:x2gobroker
+ mkdir -p /var/lib/x2gobroker
+ chown x2gobroker:x2gobroker /var/lib/x2gobroker -f
+
+ # make sure all settings are appropriate
+ if getent passwd x2gobroker | grep /dev/null 1>/dev/null 2>/dev/null; then
+ usermod --home /var/lib/x2gobroker x2gobroker
+ fi
+ if getent passwd x2gobroker | grep /bin/false 1>/dev/null 2>/dev/null; then
+ usermod --shell /bin/bash x2gobroker
+ fi
+ fi
+
+ # the x2gobroker-daemon needs special permissions on its log directory
+ if ! dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
+ dpkg-statoverride --add --update x2gobroker adm 2750 /var/log/x2gobroker
+ fi
+
+ mkdir -p /var/log/x2gobroker && chown x2gobroker:adm /var/log/x2gobroker && chmod 2755 /var/log/x2gobroker
+ touch /var/log/x2gobroker/authservice.log && chown x2gobroker:adm /var/log/x2gobroker/authservice.log
+ touch /var/log/x2gobroker/error.log && chown x2gobroker:adm /var/log/x2gobroker/error.log
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" 1>&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/x2gobroker-authservice.postrm b/debian/x2gobroker-loadchecker.postrm
similarity index 86%
copy from debian/x2gobroker-authservice.postrm
copy to debian/x2gobroker-loadchecker.postrm
index 851bfa5..588f3a1 100755
--- a/debian/x2gobroker-authservice.postrm
+++ b/debian/x2gobroker-loadchecker.postrm
@@ -1,5 +1,5 @@
#! /bin/sh
-# postrm script for x2gobroker-authservice
+# postrm script for x2gobroker-loadchecker
#
# see: dh_installdeb(1)
# summary of how this script can be called:
@@ -19,15 +19,15 @@ set -e
case "$1" in
purge)
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-agent ] ; then
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ]; then
if dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
dpkg-statoverride --remove /var/log/x2gobroker
fi
rm -Rf /var/log/x2gobroker
fi
- if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ]; then
- # remove user/group x2gobroker from system (only if not in use by x2gobroker-daemon, x2gobroker-agent, x2gobroker-wsgi)
+ if [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-wsgi ] && [ ! -d /usr/share/doc/x2gobroker-authservice ]; then
+ # remove user/group x2gobroker from system (only if not in use by x2gobroker-daemon, x2gobroker-agent, x2gobroker-wsgi, x2gobroker-authservice)
getent passwd x2gobroker 1>/dev/null && deluser x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
diff --git a/debian/x2gobroker-loadchecker.service b/debian/x2gobroker-loadchecker.service
new file mode 120000
index 0000000..426059a
--- /dev/null
+++ b/debian/x2gobroker-loadchecker.service
@@ -0,0 +1 @@
+../x2gobroker-authservice.service
\ No newline at end of file
diff --git a/debian/x2gobroker-wsgi.postrm b/debian/x2gobroker-wsgi.postrm
index 4c7c120..1299a7a 100755
--- a/debian/x2gobroker-wsgi.postrm
+++ b/debian/x2gobroker-wsgi.postrm
@@ -35,15 +35,15 @@ case "$1" in
apacheconf_remove
- if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-daemon ]; then
+ if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
if dpkg-statoverride --list /var/log/x2gobroker 1>/dev/null; then
dpkg-statoverride --remove /var/log/x2gobroker
fi
rm -Rf /var/log/x2gobroker
fi
- if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-daemon ]; then
- # remove user/group x2gobroker from system (only if not in use by x2gobroker-agent, x2gobroker-authservice, x2gobroker-daemon)
+ if [ ! -d /usr/share/doc/x2gobroker-agent ] && [ ! -d /usr/share/doc/x2gobroker-authservice ] && [ ! -d /usr/share/doc/x2gobroker-daemon ] && [ ! -d /usr/share/doc/x2gobroker-loadchecker ]; then
+ # remove user/group x2gobroker from system (only if not in use by x2gobroker-agent, x2gobroker-authservice, x2gobroker-daemon, x2gobroker-loadchecker)
getent passwd x2gobroker 1>/dev/null && deluser x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
getent group x2gobroker 1>/dev/null && delgroup x2gobroker
diff --git a/defaults/python-x2gobroker.default b/defaults/python-x2gobroker.default
index 73ee9ac..d31c113 100644
--- a/defaults/python-x2gobroker.default
+++ b/defaults/python-x2gobroker.default
@@ -37,3 +37,6 @@
# The unix socket file for communication between the broker and the authentication service.
#X2GOBROKER_AUTHSERVICE_SOCKET=/run/x2gobroker/x2gobroker-authservice.socket
+
+# The unix socket file for communication between the broker and the authentication service.
+#X2GOBROKER_LOADCHECKER_SOCKET=/run/x2gobroker/x2gobroker-loadchecker.socket
diff --git a/defaults/x2gobroker-loadchecker.default b/defaults/x2gobroker-loadchecker.default
new file mode 100644
index 0000000..b5bcd3e
--- /dev/null
+++ b/defaults/x2gobroker-loadchecker.default
@@ -0,0 +1,20 @@
+# X2Go Session Broker (Load Checker Service) configuration
+# SystemV-like init systems
+
+# For PAM authentication the X2Go Session Broker needs its authentication
+# service. The session broker itself runs as a non-privileged user (see below)
+# whereas the authentication service must run as super-user root.
+#
+# If you do not use PAM as authentication mechanism with the X2Go Session Broker,
+# you can disable the authentication service here.
+START_LOADCHECKER=false
+
+# Control debug mode (0=disable, 1=enable) of the X2Go Broker Authentication
+# Service.
+#
+# Logging is (by default) written to /var/log/x2gobroker/*log.
+#
+# This option can also be configured in /etc/default/python-x2go.
+# The value configured here overrides the value from python-x2go
+# defaults and only sets the x2gobroker-authservice into debug mode.
+#X2GOBROKER_DEBUG=0
diff --git a/etc/broker/defaults.conf b/etc/broker/defaults.conf
index 01ca667..38dfc2d 100644
--- a/etc/broker/defaults.conf
+++ b/etc/broker/defaults.conf
@@ -35,6 +35,9 @@
# The unix socket file for communication between the broker and the authentication service.
#X2GOBROKER_AUTHSERVICE_SOCKET=/run/x2gobroker/x2gobroker-authservice.socket
+# The unix socket file for communication between the broker and the authentication service.
+#X2GOBROKER_LOADCHECKER_SOCKET=/run/x2gobroker/x2gobroker-loadchecker.socket
+
[daemon]
# X2Go Session Broker configuration for Debian
diff --git a/etc/broker/x2gobroker-loadchecker-logger.conf b/etc/broker/x2gobroker-loadchecker-logger.conf
new file mode 100644
index 0000000..e18a143
--- /dev/null
+++ b/etc/broker/x2gobroker-loadchecker-logger.conf
@@ -0,0 +1,51 @@
+# This file is part of the X2Go Project - http://www.x2go.org
+# Copyright (C) 2012-2015 by Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
+#
+# X2Go Session Broker is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# X2Go Session Broker 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# WARNING: only modify this file if you _exactly_ know what you are doing!!!
+
+[loggers]
+keys=root,loadchecker
+
+[logger_root]
+level=NOTSET
+handlers=stderrHandler
+
+[handlers]
+keys=stderrHandler,loadcheckerFileHandler
+
+[formatters]
+keys=loadcheckerFormatter
+
+[handler_stderrHandler]
+class=StreamHandler
+args=(sys.stderr,)
+
+[logger_loadchecker]
+level=DEBUG
+handlers=loadcheckerFileHandler
+qualname=loadchecker
+propagate=0
+
+[handler_loadcheckerFileHandler]
+class=FileHandler
+formatter=loadcheckerFormatter
+args=('/var/log/x2gobroker/loadchecker.log',)
+
+[formatter_loadcheckerFormatter]
+format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
+datefmt=
diff --git a/etc/x2gobroker.conf b/etc/x2gobroker.conf
index 1901673..8d57ae1 100644
--- a/etc/x2gobroker.conf
+++ b/etc/x2gobroker.conf
@@ -247,6 +247,53 @@
# false in the session profile configuration).
#default-portscan-x2goservers = true
+# Use load checker for querying X2Go Servers' loads in regular intervals
+#
+# When load-balancing shall be used, the simplest way to detect "server load"
+# is counting the numbers of running and suspended sessions. No extra daemon
+# nor service is required for this.
+#
+# However, simply counting running and suspended sessions per X2Go Server
+# as a representative for the server load can be highly inaccurate. A better
+# approach is checking each X2Go Server's load in regular intervals by a
+# separate daemon (running on the broker host) and querying this load checker
+# service before selecting the best server on session startup requests.
+#
+# The load factor calculation uses this algorithm:
+#
+# ( memAvail/1000 ) * numCPUs * typeCPUs
+# load-factor = --------------------------------------
+# loadavg*100 * numSessions
+#
+# (memAvail in MByte, typeCPUs in MHz)
+#
+# The higher the load-factor, the more likely that a server will be chosen
+# for the next to be allocated X2Go session.
+#
+# If you set the default-use-load-checker option here, the queries to the
+# x2gobroker-loadchecker daemon will be performed for all broker backends by
+# defaults.
+#
+# The x2gobroker-loadchecker only gets consulted, if:
+#
+# o if enabled here for all backends
+# o or if enabled on a per broker backend basis (see below)
+#
+# and
+#
+# o the session profile defines more than one host
+# o the session profile does not block queries to the load checker daemon
+# on a per profile basis
+#
+#default-use-load-checker = false
+
+# If the x2gobroker-loadchecker daemon gets used, define here how
+# many seconds to sleep between querying system load from the
+# associated X2Go Servers.
+#
+#load-checker-intervals = 300
+
+
###
### Auth Mechs section
###
@@ -291,6 +338,7 @@
[broker_inifile]
#enable = true
#session-profiles = /etc/x2go/broker/x2gobroker-sessionprofiles.conf
+#use-load-checker = false
#[broker_ldap] -> MUSIC OF THE FUTURE
#enable = false
@@ -304,4 +352,5 @@
#group-search-filter = (&(objectClass=posifxGroup)(cn=*))
#starttls = false
#agent-query-mode = SSH
+#use-load-checker = true
diff --git a/init/x2gobroker-loadchecker.init b/init/x2gobroker-loadchecker.init
new file mode 100755
index 0000000..de8f2d0
--- /dev/null
+++ b/init/x2gobroker-loadchecker.init
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Start the X2Go Session Broker PAM Authentication Service
+#
+# Copyright © 2014 Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
+# Distributable under the terms of the GNU AGPL version 3+.
+#
+### BEGIN INIT INFO
+# Provides: x2gobroker-loadchecker
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: X2Go Session Broker PAM Authentication Service
+# Description: PAM authentication service for X2Go Session Broker
+### END INIT INFO
+#
+
+set -eu
+
+LOADCHECKER=/usr/sbin/x2gobroker-loadchecker
+test -d /run && RUNDIR=/run || RUNDIR=/var/run
+PIDFILE_LOADCHECKER=$RUNDIR/x2gobroker/x2gobroker-loadchecker.pid
+DEBIANCONFIG_COMMON=/etc/default/python-x2gobroker
+DEBIANCONFIG_LOADCHECKER=/etc/default/x2gobroker-loadchecker
+
+test -x "$LOADCHECKER" || exit 0
+
+START_LOADCHECKER=false
+X2GOBROKER_DEBUG=0
+X2GOBROKER_DAEMON_USER='x2gobroker'
+X2GOBROKER_DAEMON_GROUP='x2gobroker'
+X2GOBROKER_LOADCHECKER_SOCKET="$RUNDIR/x2gobroker/x2gobroker-loadchecker.socket"
+test -f $DEBIANCONFIG_COMMON && . $DEBIANCONFIG_COMMON
+test -f $DEBIANCONFIG_LOADCHECKER && . $DEBIANCONFIG_LOADCHECKER
+
+if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then
+ X2GOBROKER_DAEMON_USER=nobody
+fi
+if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then
+ X2GOBROKER_DAEMON_GROUP=nogroup
+fi
+
+# create PID directory
+mkdir -p $RUNDIR/x2gobroker
+chown $X2GOBROKER_DAEMON_USER:$X2GOBROKER_DAEMON_GROUP $RUNDIR/x2gobroker
+chmod 0770 $RUNDIR/x2gobroker
+
+export X2GOBROKER_DEBUG
+export X2GOBROKER_DAEMON_USER
+export X2GOBROKER_DAEMON_GROUP
+export X2GOBROKER_LOADCHECKER_SOCKET
+
+. /lib/lsb/init-functions
+
+is_true()
+{
+ case "${1:-}" in
+ [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;;
+ *) return 1;
+ esac
+}
+
+case "${1:-}" in
+ start)
+ if [ -f $PIDFILE_LOADCHECKER ]; then
+ if ps a -u root | grep -v egrep | egrep ".*$(basename $LOADCHECKER).*$X2GOBROKER_LOADCHECKER_SOCKET.*" 1>/dev/null 2>/dev/null; then
+ log_warning_msg "X2Go Broker Authentication Service already running"
+ else
+ log_warning_msg "X2Go Broker Authentication Service: stale PID file ($PIDFILE_LOADCHECKER). Delete it manually!"
+ fi
+ START_LOADCHECKER=no
+ fi
+ if is_true $START_LOADCHECKER; then
+ set +e
+ # once we are here, we have to make sure the loadchecker.socket does not exist
+ rm -f $X2GOBROKER_LOADCHECKER_SOCKET
+ # and now we can start the auth service
+ log_daemon_msg "Starting X2Go Broker Authentication Service" "$(basename $LOADCHECKER)"
+ start-stop-daemon -b -m -S -p $PIDFILE_LOADCHECKER -x $LOADCHECKER -- -s $X2GOBROKER_LOADCHECKER_SOCKET -o $X2GOBROKER_DAEMON_USER -g $X2GOBROKER_DAEMON_GROUP -p 0660
+ log_end_msg $?
+ set -e
+ fi
+ ;;
+ stop)
+ if [ -f $PIDFILE_LOADCHECKER ] ; then
+ log_daemon_msg "Stopping X2Go Broker Authentication Service" "$(basename $LOADCHECKER)"
+ set +e
+ start-stop-daemon -K -p $PIDFILE_LOADCHECKER && rm -f $PIDFILE_LOADCHECKER
+ log_end_msg $?
+ set -e
+ fi
+ ;;
+ restart|reload|force-reload)
+ ${0:-} stop
+ ${0:-} start
+ ;;
+ *)
+ echo "Usage: ${0:-} {start|stop|restart|reload|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/lib/x2gobroker-agent.pl b/lib/x2gobroker-agent.pl
index 242f78a..9463018 100755
--- a/lib/x2gobroker-agent.pl
+++ b/lib/x2gobroker-agent.pl
@@ -36,6 +36,7 @@ if ( system("x2goversion x2goserver 1>/dev/null") == 0 )
"listsessions",
"findbusyservers",
"findbusyservers_by_sessionstats",
+ "checkload",
"getservers",
"suspendsession",
"terminatesession",
@@ -133,6 +134,54 @@ if($mode eq 'ping')
exit;
}
+if ( $mode eq 'checkload' ) {
+ print "OK\n";
+
+ # Read the values from /proc/loadavg and combine all three values in a linear
+ # way and make a percent value out of the result:
+ open FILE, "< /proc/loadavg" or die return ("Cannot open /proc/loadavg: $!");
+ my ($avg1, $avg5, $avg15, undef, undef) = split / /, <FILE>;
+ close FILE;
+ my $loadavgXX = ( $avg1 + $avg5 + $avg15 ) * 100/3;
+ if ( $loadavgXX == 0 ) {
+ # load may not be ZERO
+ $loadavgXX = 1;
+ }
+
+ # calculate total memory vs. free memory
+ my $memAvail;
+ open FILE, "< /proc/meminfo" or die return ("Cannot open /proc/meminfo: $!");
+ foreach(<FILE>) {
+ if ( m/^MemAvailable:\s+(\S+)/ ) {
+ $memAvail = $1/1000;
+ last;
+ }
+ }
+ close(FILE);
+
+ # check number and type of available CPU cores
+ my $numCPU = 0;
+ my $typeCPU;
+ open FILE, "< /proc/cpuinfo" or die return ("Cannot open /proc/cpuinfo: $!");
+ foreach(<FILE>) {
+ if ( m/model name.*CPU\ \@\ ([\d\.]+)/ ) {
+ $typeCPU = $1*1000;
+ $numCPU += 1;
+ }
+ }
+ close(FILE);
+
+ print sprintf 'loadavgXX:%1$d', $loadavgXX;
+ print "\n";
+ print sprintf 'memAvail:%1$d', $memAvail;
+ print "\n";
+ print sprintf 'numCPU:%1$d', $numCPU;
+ print "\n";
+ print sprintf 'typeCPU:%1$d', $typeCPU;
+ print "\n";
+ exit;
+}
+
if($mode eq 'availabletasks')
{
print "OK\n";
diff --git a/logrotate/x2gobroker-loadchecker b/logrotate/x2gobroker-loadchecker
new file mode 100644
index 0000000..e5c8c65
--- /dev/null
+++ b/logrotate/x2gobroker-loadchecker
@@ -0,0 +1,14 @@
+/var/log/x2gobroker/loadchecker.log {
+ weekly
+ missingok
+ rotate 52
+ compress
+ delaycompress
+ notifempty
+ create 640 root adm
+ su root adm
+ sharedscripts
+ postrotate
+ invoke-rc.d x2gobroker-loadchecker restart > /dev/null
+ endscript
+}
diff --git a/man/man8/x2gobroker-authservice.8 b/man/man8/x2gobroker-authservice.8
index eff8569..8981ee6 100644
--- a/man/man8/x2gobroker-authservice.8
+++ b/man/man8/x2gobroker-authservice.8
@@ -31,7 +31,7 @@ Thus, the PAM authentication has been moved into a separate service. The communi
between X2Go Session Broker and PAM Authentication Service is handled through a
unix domain socket file (\fI<RUNDIR>/x2gobroker/x2gobroker-authservice.socket\fR).
.PP
-This command is normally started through an init script.
+This command is normally started through the host's init system.
.SH COMMON OPTIONS
\fBx2gobroker-authservice\fR accepts the following common options:
.TP
diff --git a/man/man8/x2gobroker-loadchecker.8 b/man/man8/x2gobroker-loadchecker.8
new file mode 100644
index 0000000..f025f5e
--- /dev/null
+++ b/man/man8/x2gobroker-loadchecker.8
@@ -0,0 +1,93 @@
+'\" -*- coding: utf-8 -*-
+.if \n(.g .ds T< \\FC
+.if \n(.g .ds T> \\F[\n[.fam]]
+.de URL
+\\$2 \(la\\$1\(ra\\$3
+..
+.if \n(.g .mso www.tmac
+.TH x2gobroker-loadchecker 8 "Mar 2015" "Version 0.0.3.x" "X2Go Session Broker"
+.SH NAME
+x2gobroker-loadchecker \- Load checker service for X2Go Session Broker
+.SH SYNOPSIS
+'nh
+.fi
+.ad l
+\fBx2gobroker-loadchecker\fR \kx
+.if (\nx>(\n(.l/2)) .nr x (\n(.l/5)
+'in \n(.iu+\nxu
+[
+\fIoptions\fR
+]
+'in \n(.iu-\nxu
+.ad b
+'hy
+.SH DESCRIPTION
+\fBx2gobroker-loadchecker\fR is a service that collects system load metrics from
+broker-associated X2Go Servers.
+.PP
+When load-balancing shall be used, the simplest way to detect "server load"
+is counting the numbers of running and suspended sessions. No extra daemon
+nor service is required for this. The server with the least amount of sessions
+will be selected for starting the next X2Go session.
+.PP
+However, simply counting running and suspended sessions per X2Go Server
+as a representative for the server load can be highly inaccurate. A better
+approach is checking each X2Go Server's load in regular intervals by the
+\fBx2gobroker-loadchecker\fR daemon (running on the broker host) and querying
+the \fBx2gobroker-loadchecker\fR daemon before selecting the best server on
+session startup requests.
+.PP
+The \fBx2gobroker-loadchecker\fR collects server metrics of all
+associated X2Go Servers and keeps the latest load factors in RAM. Once
+the broker needs load factors of a certain session profile (i.e., of all
+servers configured in that session profile), the
+\fBx2gobroker-loadchecker\fR delivers that info immediately. The
+remembered load factors may not be 100% up-to-date (default: collected
+within the last five minutes), but the response time of the load checker
+is much faster than a query to all possible X2Go Servers would be.
+.PP
+The load factor calculation uses this algorithm:
+.PP
+ ( memAvail[MByte]/1000 ) * numCPUs * typeCPUs[MHz]
+ load-factor = --------------------------------------------------
+ loadavg*100 * numSessions
+.PP
+The higher the load-factor, the more likely that a server will be chosen
+for the next to be allocated X2Go session.
+.PP
+The communication
+between X2Go Session Broker and the Load Checker Service is handled through a
+unix domain socket file (\fI<RUNDIR>/x2gobroker/x2gobroker-loadchecker.socket\fR).
+.PP
+This command is normally started through the host's init system.
+.SH COMMON OPTIONS
+\fBx2gobroker-loadchecker\fR accepts the following common options:
+.TP
+\*(T<\fB\-h, \-\-help\fR\*(T>
+Display a help with all available command line options and exit.
+.TP
+\*(T<\fB\-D, \-\-daemonize\fR\*(T>
+Fork this application to background and detach from the running terminal.
+.TP
+\*(T<\fB\-P, \-\-pidfile\fR\*(T>
+Custom PID file location when daemonizing (default: \fI<RUNDIR>/x2gobroker/x2gobroker-loadchecker.pid\fR).
+.TP
+\*(T<\fB\-L, \-\-logdir\fR\*(T>
+Directory where stdout/stderr will be redirected after having daemonized (default: \fI/var/log/x2gobroker/\fR).
+.TP
+\*(T<\fB\-s <LOADCHECKERSOCKET>, \-\-socket <LOADCHECKERSOCKET>\fR\*(T>
+File name of the unix domain socket file used for communication between broker and load checker service.
+.TP
+\*(T<\fB\-o <OWNER>, \-\-owner <OWNER>\fR\*(T>
+User ownership of the \fI<LOADCHECKERSOCKET>\fR file.
+.TP
+\*(T<\fB\-g <GROUP>, \-\-group <GROUP>\fR\*(T>
+Group ownership of the \fI<LOADCHECKERSOCKET>\fR file.
+.TP
+\*(T<\fB\-p <PERMISSIONS>, \-\-permissions <PERMISSIONS>\fR\*(T>
+Set these file permissions for the \fI<LOADCHECKERSOCKET>\fR file. Use numerical permissions (e.g. 0640).
+.SH "FILES"
+<RUNDIR>/x2gobroker/x2gobroker-loadchecker.socket
+.SH AUTHOR
+This manual has been written for the X2Go project by
+Mike Gabriel <mike.gabriel at das-netzwerkteam.de>.
diff --git a/sbin/x2gobroker-loadchecker b/sbin/x2gobroker-loadchecker
new file mode 100755
index 0000000..8d6ae32
--- /dev/null
+++ b/sbin/x2gobroker-loadchecker
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of the X2Go Project - http://www.x2go.org
+# Copyright (C) 2012-2015 by Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
+#
+# X2Go Session Broker is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# X2Go Session Broker 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import os
+import sys
+import setproctitle
+import argparse
+import logging
+import asyncore
+import socket
+import getpass
+import logging.config
+import atexit
+import ConfigParser
+
+if os.path.isdir('/run'):
+ RUNDIR = '/run'
+else:
+ RUNDIR = '/var/run'
+
+try:
+ import daemon
+ import lockfile
+ CAN_DAEMONIZE = True
+ pidfile = '{run}/x2gobroker/x2gobroker-loadchecker.pid'.format(run=RUNDIR)
+ daemon_logdir = '/var/log/x2gobroker/'
+except ImportError:
+ CAN_DAEMONIZE = False
+
+from pwd import getpwnam
+from grp import getgrnam
+
+PROG_NAME = os.path.basename(sys.argv[0])
+PROG_OPTIONS = sys.argv[1:]
+setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS)))
+
+from x2gobroker import __VERSION__
+from x2gobroker import __AUTHOR__
+import x2gobroker.loadchecker
+
+global load_checker
+
+class LoadCheckerServiceHandler(asyncore.dispatcher_with_send):
+
+ def __init__(self, sock, logger=None):
+ self.logger = logger
+ asyncore.dispatcher_with_send.__init__(self, sock)
+ self._buf = ''
+
+ def handle_read(self):
+ data = self._buf + self.recv(1024)
+ if not data:
+ self.close()
+ return
+ reqs, data = data.rsplit('\n', 1)
+ self._buf = data
+ output = ""
+ for req in reqs.split('\n'):
+ backend, profile_id, hostname = req.split('\r')
+ if self.logger: self.logger.debug('LoadCheckServiceHandler.handle_read(): received load check query: backend={backend}, profile_id={profile_id}, hostname={hostname}'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ if hostname:
+ load_factor = load_checker.get_server_load(backend, profile_id, hostname)
+ if load_factor is not None:
+ output += "{lf}".format(lf=load_factor)
+ if self.logger: self.logger.info('LoadCheckServiceHandler.handle_read(): load check result for backend={backend}, profile_id={profile_id}, hostname={hostname}: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factor))
+ else:
+ output += "LOAD-UNAVAILABLE"
+ if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}, hostname={hostname}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ else:
+ load_factors = load_checker.get_profile_load(backend, profile_id)
+ if load_factors:
+ for h in load_factors.keys():
+ if load_factors[h] is not None:
+ output +="{hostname}:{loadfactor}".format(hostname=h, loadfactor=load_factors[h])
+ if self.logger: self.logger.info('LoadCheckServiceHandler.handle_read(): load check result for backend={backend}, profile_id={profile_id}, hostname={hostname}: {lf}'.format(backend=backend, profile_id=profile_id, hostname=h, lf=load_factors[h]))
+ else:
+ output += "{hostname}:LOAD-UNAVAILABLE".formate(hostname=h)
+ if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}, hostname={hostname}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id, hostname=h))
+ else:
+ output += "LOAD-UNAVAILABLE"
+ if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id))
+ self.send(output)
+
+ def handle_close(self):
+ self.close()
+
+
+class LoadCheckerService(asyncore.dispatcher_with_send):
+
+ def __init__(self, socketfile, owner='root', group_owner='root', permissions='0660', logger=None):
+ self.logger = logger
+ asyncore.dispatcher_with_send.__init__(self)
+ self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind(socketfile)
+ os.chown(socketfile, getpwnam(owner).pw_uid, getgrnam(group_owner).gr_gid)
+ os.chmod(socketfile, int(permissions, 8))
+ self.listen(1)
+
+ def handle_accept(self):
+ conn, _ = self.accept()
+ LoadCheckerServiceHandler(conn, logger=self.logger)
+
+
+def loop():
+ ### the "loop" has two tasks...
+
+ # 1. Do regular queries to remote X2Go Broker Agent instances to collect
+ # load average, CPU usage and type, memory usage, etc.
+ load_checker.start()
+
+ # 2. Provide a listening UNIX domain socket file that can be used for querying
+ # server states.
+ asyncore.loop()
+
+
+def cleanup_on_exit():
+ os.remove(X2GOBROKER_LOADCHECKER_SOCKET)
+ try: os.remove(pidfile)
+ except: pass
+
+
+# load the defaults.conf file, if present
+iniconfig_loaded = None
+iniconfig_section = '-'.join(PROG_NAME.split('-')[1:])
+X2GOBROKER_DEFAULTS = "/etc/x2go/broker/defaults.conf"
+if os.path.isfile(X2GOBROKER_DEFAULTS) and os.access(X2GOBROKER_DEFAULTS, os.R_OK):
+ iniconfig = ConfigParser.SafeConfigParser()
+ iniconfig.optionxform = str
+ iniconfig_loaded = iniconfig.read(X2GOBROKER_DEFAULTS)
+
+# normally this would go into defaults.py, however, we do not want to pull in defaults.py here as that will create
+# unwanted logfiles (access.log, broker.log, error.log) when x2gobroker-loadchecker is installed as standalone service
+if os.environ.has_key('X2GOBROKER_DEBUG'):
+ X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) )
+elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEBUG'):
+ X2GOBROKER_DEBUG=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEBUG')
+elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEBUG'):
+ X2GOBROKER_DEBUG=iniconfig.get('common', 'X2GOBROKER_DEBUG')
+else:
+ X2GOBROKER_DEBUG = False
+
+if os.environ.has_key('X2GOBROKER_DAEMON_USER'):
+ X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER']
+elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DAEMON_USER'):
+ X2GOBROKER_DAEMON_USER=iniconfig.get(iniconfig_section, 'X2GOBROKER_DAEMON_USER')
+elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DAEMON_USER'):
+ X2GOBROKER_DAEMON_USER=iniconfig.get('common', 'X2GOBROKER_DAEMON_USER')
+else:
+ X2GOBROKER_DAEMON_USER="x2gobroker"
+
+if os.environ.has_key('X2GOBROKER_LOADCHECKER_LOGCONFIG'):
+ X2GOBROKER_LOADCHECKER_LOGCONFIG=os.environ['X2GOBROKER_LOADCHECKER_LOGCONFIG']
+elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_LOGCONFIG'):
+ X2GOBROKER_LOADCHECKER_LOGCONFIG=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_LOGCONFIG')
+elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_LOGCONFIG'):
+ X2GOBROKER_LOADCHECKER_LOGCONFIG=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_LOGCONFIG')
+else:
+ X2GOBROKER_LOADCHECKER_LOGCONFIG="/etc/x2go/broker/x2gobroker-loadchecker-logger.conf"
+
+if os.environ.has_key('X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=os.environ['X2GOBROKER_LOADCHECKER_SOCKET']
+elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET')
+elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_SOCKET')
+else:
+ X2GOBROKER_LOADCHECKER_SOCKET="{run}/x2gobroker/x2gobroker-loadchecker.socket".format(run=RUNDIR)
+
+
+if __name__ == '__main__':
+
+ common_options = [
+ {'args':['-s','--socket-file'], 'default': X2GOBROKER_LOADCHECKER_SOCKET, 'metavar': 'LOADCHECKERSOCKET', 'help': 'socket file for LoadChecker communication', },
+ {'args':['-o','--owner'], 'default': 'root', 'help': 'owner of the LoadChecker socket file', },
+ {'args':['-g','--group'], 'default': 'root', 'help': 'group ownership of the LoadChecker socket file', },
+ {'args':['-p','--permissions'], 'default': '0660', 'help': 'set these file permissions for the LoadChecker socket file', },
+ {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code', },
+ {'args':['-i','--debug-interactively'], 'default': False, 'action': 'store_true', 'help': 'force output of log message to the stderr (rather than to the log files)', },
+
+ ]
+ if CAN_DAEMONIZE:
+ common_options.extend([
+ {'args':['-D', '--daemonize'], 'default': False, 'action': 'store_true', 'help': 'Detach the X2Go Broker process from the current terminal and fork to background', },
+ {'args':['-P', '--pidfile'], 'default': pidfile, 'help': 'Alternative file path for the daemon\'s PID file', },
+ {'args':['-L', '--logdir'], 'default': daemon_logdir, 'help': 'Directory where log files for the process\'s stdout and stderr can be created', },
+ ])
+ p = argparse.ArgumentParser(description='X2Go Session Broker (Load Checker Service)',\
+ formatter_class=argparse.RawDescriptionHelpFormatter, \
+ add_help=True, argument_default=None)
+ p_common = p.add_argument_group('common parameters')
+
+ for (p_group, opts) in ( (p_common, common_options), ):
+ for opt in opts:
+ args = opt['args']
+ del opt['args']
+ p_group.add_argument(*args, **opt)
+
+ cmdline_args = p.parse_args()
+
+ # standalone daemon mode (x2gobroker-loadchecker as daemon) or interactive mode (called from the cmdline)?
+ if getpass.getuser() in (X2GOBROKER_DAEMON_USER, 'root') and not cmdline_args.debug_interactively:
+
+ # we run in standalone daemon mode, so let's use the system configuration for logging
+ logging.config.fileConfig(X2GOBROKER_LOADCHECKER_LOGCONFIG)
+
+ # create loadchecker logger
+ logger_loadchecker = logging.getLogger('loadchecker')
+
+ else:
+ logger_root = logging.getLogger()
+ stderr_handler = logging.StreamHandler(sys.stderr)
+ stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt=''))
+
+ # all loggers stream to stderr...
+ logger_root.addHandler(stderr_handler)
+
+ logger_loadchecker = logging.getLogger('loadchecker')
+ logger_loadchecker.addHandler(stderr_handler)
+ logger_loadchecker.propagate = 0
+
+ if cmdline_args.debug_interactively:
+ cmdline_args.debug = True
+
+ # raise log level to DEBUG if requested...
+ if cmdline_args.debug or X2GOBROKER_DEBUG:
+ X2GOBROKER_DEBUG = True
+ logger_loadchecker.setLevel(logging.DEBUG)
+
+ logger_loadchecker.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__))
+ logger_loadchecker.info('Setting up the Load Checker service\'s environment...')
+ logger_loadchecker.info(' X2GOBROKER_DEBUG: {value}'.format(value=X2GOBROKER_DEBUG))
+ logger_loadchecker.info(' X2GOBROKER_LOADCHECKER_SOCKET: {value}'.format(value=X2GOBROKER_LOADCHECKER_SOCKET))
+
+ load_checker = x2gobroker.loadchecker.LoadChecker(logger=logger_loadchecker)
+
+ if CAN_DAEMONIZE and cmdline_args.daemonize:
+
+ # create directory for the PID file
+ pidfile = os.path.expanduser(cmdline_args.pidfile)
+ if not os.path.isdir(os.path.dirname(pidfile)):
+ try:
+ os.makedirs(os.path.dirname(pidfile))
+ except:
+ pass
+ if not os.access(os.path.dirname(pidfile), os.W_OK) or (os.path.exists(pidfile) and not os.access(pidfile, os.W_OK)):
+ print("")
+ p.print_usage()
+ print("Insufficent privileges. Cannot create PID file {pidfile} path".format(pidfile=pidfile))
+ print("")
+ sys.exit(-3)
+
+ # create directory for logging
+ daemon_logdir = os.path.expanduser(cmdline_args.logdir)
+ if not os.path.isdir(daemon_logdir):
+ try:
+ os.makedirs(daemon_logdir)
+ except:
+ pass
+ if not os.access(daemon_logdir, os.W_OK):
+ print("")
+ p.print_usage()
+ print("Insufficent privileges. Cannot create directory for stdout/stderr log files: {logdir}".format(logdir=daemon_logdir))
+ print("")
+ sys.exit(-3)
+ else:
+ if not daemon_logdir.endswith('/'):
+ daemon_logdir += '/'
+
+ socket_file = cmdline_args.socket_file
+
+ if os.path.exists(socket_file):
+ os.remove(socket_file)
+
+ if not os.path.exists(os.path.dirname(socket_file)):
+ os.makedirs(os.path.dirname(socket_file))
+
+ runtimedir_permissions = int(cmdline_args.permissions, 8)
+ if runtimedir_permissions & 0400: runtimedir_permissions = runtimedir_permissions | 0100
+ if runtimedir_permissions & 0040: runtimedir_permissions = runtimedir_permissions | 0010
+ if runtimedir_permissions & 0004: runtimedir_permissions = runtimedir_permissions | 0001
+ try:
+ os.chown(os.path.dirname(socket_file), getpwnam(cmdline_args.owner).pw_uid, getpwnam(cmdline_args.group).pw_gid)
+ os.chmod(os.path.dirname(socket_file), runtimedir_permissions)
+ except OSError:
+ pass
+
+ LoadCheckerService(socket_file, owner=cmdline_args.owner, group_owner=cmdline_args.group, permissions=cmdline_args.permissions, logger=logger_loadchecker)
+ atexit.register(cleanup_on_exit)
+ try:
+ if CAN_DAEMONIZE and cmdline_args.daemonize:
+ keep_fds = [int(fd) for fd in os.listdir('/proc/self/fd') if fd not in (0,1,2) ]
+ daemon_stdout = file(daemon_logdir+'x2gobroker-loadchecker.stdout', 'w+')
+ daemon_stderr = file(daemon_logdir+'x2gobroker-loadchecker.stderr', 'w+')
+ with daemon.DaemonContext(stdout=daemon_stdout, stderr=daemon_stderr, files_preserve=keep_fds, umask=0o027, pidfile=lockfile.FileLock(pidfile), detach_process=True):
+ file(pidfile, 'w+').write(str(os.getpid())+"\n")
+ loop()
+ else:
+ loop()
+ except KeyboardInterrupt:
+ pass
diff --git a/x2gobroker-loadchecker.service b/x2gobroker-loadchecker.service
new file mode 100644
index 0000000..692e15f
--- /dev/null
+++ b/x2gobroker-loadchecker.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=X2Go Session Broker Load Checker Service
+
+[Service]
+User=root
+Group=root
+Type=forking
+ExecStart=/usr/sbin/x2gobroker-loadchecker --socket-file=/run/x2gobroker/x2gobroker-loadchecker.socket --daemonize -o root -g x2gobroker -p 0660 --pidfile=/run/x2gobroker/x2gobroker-loadchecker.pid
+PIDFile=/run/x2gobroker/x2gobroker-loadchecker.pid
+
+[Install]
+WantedBy=multi-user.target
diff --git a/x2gobroker.spec b/x2gobroker.spec
index bcb398f..55b1fdb 100644
--- a/x2gobroker.spec
+++ b/x2gobroker.spec
@@ -14,6 +14,7 @@ Url: http://www.x2go.org/
Source0: http://code.x2go.org/releases/source/%name/%name-%version.tar.gz
Source1: x2gobroker-daemon.init
Source2: x2gobroker-authservice.init
+Source2: x2gobroker-loadchecker.init
Source3: x2gobroker-rpmlintrc
%if 0%{?el5}
@@ -147,6 +148,46 @@ A session broker is most useful in load balanced X2Go server farms.
This package contains the authentication service against the PAM system.
+%package loadchecker
+Summary: X2Go Session Broker (load checker service)
+%if 0%{?suse_version}
+Group: Productivity/Networking/Remote Desktop
+%else
+Group: Applications/Communications
+%endif
+%if 0%{?suse_version}
+Requires: python
+%else
+Requires: python2
+%endif
+Requires: python-argparse
+Requires: python-setproctitle
+Requires: logrotate
+Requires(pre): python-x2gobroker = %{version}-%{release}
+%if 0%{?suse_version}
+Requires(pre): permissions
+%endif
+
+%description loadchecker
+X2Go is a server based computing environment with
+ - session resuming
+ - low bandwidth support
+ - session brokerage support
+ - client side mass storage mounting support
+ - client side printing support
+ - audio support
+ - authentication by smartcard and USB stick
+
+The session broker is a server tool for X2Go that tells your X2Go Client
+application in a terminal server cluster what servers and session types are
+most appropriate for the user in front of the X2Go terminal.
+
+A session broker is most useful in load balanced X2Go server farms.
+
+This package contains the load checker service required for broker setups
+with dynamic load balancing.
+
+
%package daemon
Summary: X2Go Session Broker (standalone daemon)
%if 0%{?suse_version}
@@ -346,6 +387,8 @@ grep -l -r -E '^#!/usr/bin/env python$' | while read file; do \
%endif
sed -i logrotate/x2gobroker-authservice \
-e 's/adm/root/'
+sed -i logrotate/x2gobroker-loadchecker \
+ -e 's/adm/root/'
sed -i logrotate/x2gobroker-daemon \
-e 's/adm/root/'
sed -i logrotate/x2gobroker-wsgi \
@@ -379,14 +422,18 @@ ln -s "%_sysconfdir/x2go/x2gobroker-wsgi.apache.vhost" \
mkdir -p %{buildroot}%{_unitdir}
install -pm0644 x2gobroker-daemon.service %{buildroot}%{_unitdir}
install -pm0644 x2gobroker-authservice.service %{buildroot}%{_unitdir}
+install -pm0644 x2gobroker-loadchecker.service %{buildroot}%{_unitdir}
rm -f %{buildroot}%{_sysconfdir}/default/x2gobroker-daemon
rm -f %{buildroot}%{_sysconfdir}/default/x2gobroker-authservice
+rm -f %{buildroot}%{_sysconfdir}/default/x2gobroker-loadchecker
rm -f %{buildroot}%{_sysconfdir}/default/python-x2gobroker
%else
# SysV session cleanup script
%if 0%{?el5}
rm -f %{buildroot}%{_sysconfdir}/x2go/broker/defaults.conf
mkdir -p %{buildroot}%{_initrddir}
+install -pm0755 %SOURCE3 \
+ "$b/%_initrddir/x2gobroker-loadchecker"
install -pm0755 %SOURCE2 \
"$b/%_initrddir/x2gobroker-authservice"
install -pm0755 %SOURCE1 \
@@ -395,6 +442,8 @@ install -pm0755 %SOURCE1 \
%if 0%{?el6} || ( 0%{?suse_version} && 0%{?suse_version} < 1140)
rm -f %{buildroot}%{_sysconfdir}/x2go/broker/defaults.conf
mkdir -p "$b/%_initddir"
+install -pm0755 %SOURCE3 \
+ "$b/%_initddir/x2gobroker-loadchecker"
install -pm0755 %SOURCE2 \
"$b/%_initddir/x2gobroker-authservice"
install -pm0755 %SOURCE1 \
@@ -480,6 +529,56 @@ fi
%if 0%{?suse_version} >= 1230
+%pre loadchecker
+%service_add_pre x2gobroker-loadchecker.service
+%endif
+
+%post loadchecker
+%if 0%{?fedora} || 0%{?el7} || 0%{?suse_version} >= 1230
+%if 0%{?suse_version}
+%service_add_post x2gobroker-loadchecker.service
+%else
+%systemd_post x2gobroker-loadchecker.service
+%endif
+%else
+/sbin/chkconfig --add x2gobroker-loadchecker
+if [ "$1" -ge "1" ] ; then
+ /sbin/service x2gobroker-loadchecker condrestart >/dev/null 2>&1 || :
+fi
+%endif
+%if 0%{?suse_version}
+%set_permissions %{_localstatedir}/log/x2gobroker
+
+
+%verifyscript loadchecker
+%verify_permissions -e %{_localstatedir}/log/x2gobroker
+%endif
+
+%preun loadchecker
+%if 0%{?fedora} || 0%{?el7} || 0%{?suse_version} >= 1230
+%if 0%{?suse_version}
+%service_del_preun x2gobroker-loadchecker.service
+%else
+%systemd_preun x2gobroker-loadchecker.service
+%endif
+%else
+if [ "$1" = 0 ]; then
+ /sbin/service x2gobroker-loadchecker stop >/dev/null 2>&1
+ /sbin/chkconfig --del x2gobroker-loadchecker
+fi
+%endif
+
+%if 0%{?fedora} || 0%{?el7} || 0%{?suse_version} >= 1230
+%postun loadchecker
+%if 0%{?suse_version}
+%service_del_postun x2gobroker-loadchecker.service
+%else
+%systemd_postun x2gobroker-loadchecker.service
+%endif
+%endif
+
+
+%if 0%{?suse_version} >= 1230
%pre daemon
%service_add_pre x2gobroker-daemon.service
%endif
@@ -620,6 +719,26 @@ fi
%attr(02750,x2gobroker,x2gobroker) %_localstatedir/log/x2gobroker
+%files loadchecker
+%defattr(-,root,root)
+%if 0%{?el5}
+%_initrddir/x2gobroker-loadchecker
+%endif
+%if 0%{?el6}
+%_initddir/x2gobroker-loadchecker
+%endif
+%if 0%{?fedora} || 0%{?el7} || 0%{?suse_version} >= 1230
+%{_unitdir}/x2gobroker-loadchecker.service
+%endif
+%if 0%{?el5} || 0%{?el6} || (0%{?suse_version} && 0%{?suse_version} < 1140)
+%config %_sysconfdir/default/x2gobroker-loadchecker
+%endif
+%config %_sysconfdir/logrotate.d/x2gobroker-loadchecker
+%_sbindir/x2gobroker-loadchecker
+%_mandir/man8/x2gobroker-loadchecker.8*
+%attr(02750,x2gobroker,x2gobroker) %_localstatedir/log/x2gobroker
+
+
%files daemon
%defattr(-,root,root)
%_bindir/x2gobroker-daemon
diff --git a/x2gobroker/agent.py b/x2gobroker/agent.py
index 5364825..462f188 100644
--- a/x2gobroker/agent.py
+++ b/x2gobroker/agent.py
@@ -338,6 +338,31 @@ def find_busy_servers(username, remote_agent=None, **kwargs):
tasks['findbusyservers'] = find_busy_servers
+def checkload(remote_agent=None, **kwargs):
+ """\
+ Query X2Go Broker Agent for a summary of system load specific
+ parameters.
+
+ @param remote_agent: information about the remote agent that is to be called.
+ @type remote_agent: C{dict}
+
+ """
+ _success, _load_params = call_broker_agent('foo', task='checkload', remote_agent=remote_agent, **kwargs)
+
+ p = {}
+ for _param in _load_params:
+ if ':' in _param:
+ key, val = _param.split(':', 1)
+ p[key] = float(val)
+
+ load_factor = None
+ try:
+ load_factor = long( ( (p['memAvail']/1000) * p['numCPU'] * p['typeCPU'] * 100 ) / p['loadavgXX'] )
+ except KeyError:
+ pass
+
+ return load_factor
+
def add_authorized_key(username, pubkey_hash, authorized_keys_file='%h/.x2go/authorized_keys', remote_agent=None, **kwargs):
"""\
Add a public key hash to the user's authorized_keys file.
diff --git a/x2gobroker/brokers/base_broker.py b/x2gobroker/brokers/base_broker.py
index fe9ecc5..ad22803 100644
--- a/x2gobroker/brokers/base_broker.py
+++ b/x2gobroker/brokers/base_broker.py
@@ -708,6 +708,59 @@ class X2GoBroker(object):
return unicode(_group_db)
+ def get_use_load_checker(self):
+ """\
+ Is this broker backend configured to access an X2Go Broker LoadChecker daemon.
+
+ @return: C{True} if there should a load checker daemon running.
+ @rtype: C{bool}
+
+ """
+ _use_load_checker = False
+ if self.config.has_value('global', 'default-use-load-checker'):
+ _use_load_checker = self.config.get_value('global', 'default-use-load-checker') or _use_load_checker
+
+ if self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'use-load-checker'):
+ _use_load_checker = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'use-load-checker') or _use_load_checker
+
+ return _use_load_checker
+
+ def use_load_checker(self, profile_id):
+ """\
+ Actually query the load checker daemon for the given session profile ID.
+ This method will check:
+
+ - broker backend configured to use load checker daemon?
+ - more than one host configured?
+ - load checker queries not explicitly disabled in session profile?
+
+ @param profile_id: choose remote agent for this profile ID
+ @type profile_id: C{unicode}
+
+ @return: C{True} if there is a load checker daemon running.
+ @rtype: C{bool}
+
+ """
+ # only use load checker if...
+
+ # it is enabled for the broker backend...
+ if self.get_use_load_checker():
+
+ _profile = self.get_profile(profile_id)
+
+ # it is not explicitly disabled per session profile definition
+ if _profile and _profile.has_key(u'broker-use-load-checker') and _profile['broker-use-load-checker'] is False:
+ return False
+
+ # more than one host is defined in the session profile
+ if len(_profile[u'host']) < 2:
+ return False
+
+ else:
+ return False
+
+ return True
+
def _import_nameservice_module(self, service='libnss'):
try:
if self.nameservice_module is None:
@@ -1046,6 +1099,49 @@ class X2GoBroker(object):
return remote_agent
+ def get_all_remote_agents(self, profile_id):
+ """\
+ Get all remote agents.
+
+ @param profile_id: choose remote agent for this profile ID
+ @type profile_id: C{unicode}
+
+ @return: C{list} of remote agents for the given profile ID
+ @rtype: C{list}
+
+ """
+ remote_agents = []
+
+ # no remote agent needed for shadow sessions
+ if self.is_shadow_profile(profile_id):
+ return remote_agents
+
+ agent_query_mode = self.get_agent_query_mode(profile_id).upper()
+ if agent_query_mode == u'SSH' and x2gobroker.agent.has_remote_broker_agent_setup():
+
+ profile = self.get_profile(profile_id)
+ server_list = profile[u'host']
+
+ while server_list:
+
+ remote_agent_hostname = server_list[-1]
+ remote_agent_hostaddr = remote_agent_hostname
+ remote_agent_port = profile[u'sshport']
+ if profile.has_key('sshport={hostname}'.format(hostname=remote_agent_hostname)):
+ remote_agent_port = profile["sshport={hostname}".format(hostname=remote_agent_hostname)]
+ if profile.has_key('host={hostname}'.format(hostname=remote_agent_hostname)):
+ remote_agent_hostaddr = profile["host={hostname}".format(hostname=remote_agent_hostname)]
+
+ remote_agents.append({
+ u'hostname': remote_agent_hostname,
+ u'hostaddr': remote_agent_hostaddr,
+ u'port': remote_agent_port, }
+ )
+
+ server_list = server_list[0:-1]
+
+ return remote_agents
+
def is_shadow_profile(self, profile_id):
"""\
Detect from the session profile, if it defines a desktop sharing (shadow)
@@ -1357,6 +1453,25 @@ class X2GoBroker(object):
if busy_server not in server_list:
del busy_servers[busy_server]
+
+ # dynamic load-balancing
+ if self.use_load_checker(self, profile_id):
+ busy_servers_temp = copy.deepcopy(busy_servers)
+ load_factors = x2gobroker.loadchecker.checkload(self.backend_name, profile_id)
+ if load_factors:
+ for busy_server in busy_servers_temp.keys():
+ # FIXME: this may need love later for peculiar multi-farm setups with non-FQDN hostnames in session profile configurations
+ if busy_server in load_factors.keys() and type(load_factors[busy_server]) == types.LongType:
+ # do the load-factor / relX2GoServerUsage calculation here...
+ busy_servers_temp[busy_server] = load_factors[busy_server] / busy_servers[busy_server]
+ else:
+ # ignore the load checker, results are garbage...
+ busy_servers_temp = None
+ break
+
+ if busy_servers_temp is not None:
+ busy_servers = copy.deepcopy(busy_servers_temp)
+
busy_server_list = [ (load, server) for server, load in busy_servers.items() ]
busy_server_list.sort()
diff --git a/x2gobroker/defaults.py b/x2gobroker/defaults.py
index c7d4dbf..a6d4830 100644
--- a/x2gobroker/defaults.py
+++ b/x2gobroker/defaults.py
@@ -131,6 +131,19 @@ else:
RUNDIR = '/var/run/x2gobroker'
X2GOBROKER_AUTHSERVICE_SOCKET="{run}/x2gobroker/x2gobroker-authservice.socket".format(run=RUNDIR)
+if os.environ.has_key('X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=os.environ['X2GOBROKER_LOADCHECKER_SOCKET']
+elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET')
+elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_SOCKET'):
+ X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_SOCKET')
+else:
+ if os.path.isdir('/run/x2gobroker'):
+ RUNDIR = '/run'
+ else:
+ RUNDIR = '/var/run/x2gobroker'
+ X2GOBROKER_LOADCHECKER_SOCKET="{run}/x2gobroker/x2gobroker-loadchecker.socket".format(run=RUNDIR)
+
if os.environ.has_key('X2GOBROKER_DEFAULT_BACKEND'):
X2GOBROKER_DEFAULT_BACKEND = os.environ['X2GOBROKER_DEFAULT_BACKEND']
elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEFAULT_BACKEND'):
@@ -210,6 +223,8 @@ X2GOBROKER_CONFIG_DEFAULTS = {
u'default-sshproxy-authorized-keys': u'%h/.x2go/authorized_keys',
u'default-agent-query-mode': u'NONE',
u'default-portscan-x2goservers': True,
+ u'default-use-load-checker': False,
+ u'load-checker-intervals': 300,
},
'broker_base': {
u'enable': False,
@@ -220,6 +235,7 @@ X2GOBROKER_CONFIG_DEFAULTS = {
u'user-db': u'libnss',
u'group-db': u'libnss',
u'desktop-shell': u'KDE',
+ u'load-checker': False,
},
'broker_inifile': {
u'enable': True,
@@ -227,6 +243,7 @@ X2GOBROKER_CONFIG_DEFAULTS = {
u'auth-mech': u'',
u'user-db': u'',
u'group-db': u'',
+ u'use-load-checker': True,
},
'broker_ldap': {
u'enable': False,
@@ -240,6 +257,7 @@ X2GOBROKER_CONFIG_DEFAULTS = {
u'group-search-filter': u'(&(objectClass=posifxGroup)(cn=*))',
u'starttls': False,
u'agent-query-mode': u'SSH',
+ u'load-checker': True,
},
}
diff --git a/x2gobroker/loadchecker.py b/x2gobroker/loadchecker.py
new file mode 100644
index 0000000..f38717e
--- /dev/null
+++ b/x2gobroker/loadchecker.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+
+# This file is part of the X2Go Project - http://www.x2go.org
+# Copyright (C) 2012-2015 by Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
+#
+# X2Go Session Broker is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# X2Go Session Broker 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import threading
+import time
+import copy
+import socket
+
+# X2Go Session Broker modules
+import x2gobroker.defaults
+import x2gobroker.config
+from x2gobroker.loggers import logger_broker
+
+
+def checkload(backend, profile_id, hostname=None):
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ logger_broker.debug('loadchecker.checkload(): connecting to load checker service socket {socket}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET))
+ try:
+ s.connect(x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET)
+ except socket.error, e:
+ logger_broker.error('loadchecker.checkload(): failure when connecting to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e)))
+
+ if hostname is not None:
+ load_factor = 'LOAD-UNAVAILABLE'
+ logger_broker.debug('loadchecker.checkload(): sending backend={backend}, profile_id={profile_id}, hostname={hostname} to load checker service'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ try:
+ s.send('{backend}\r{profile_id}\r{hostname}\n'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ load_factor = s.recv(1024)
+ s.close()
+ except socket.error, e:
+ logger_broker.error('loadchecker.checkload(): failure when sending data to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e)))
+
+ if load_factor.startswith('LOAD-UNAVAILABLE'):
+ logger_broker.warning('loadchecker.checkload(): load unavailable for backend={backend}, profile_id={profile_id}, hostname={hostname}'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ return None
+
+ logger_broker.info('loadchecker.checkload(): load factor for backend={backend}, profile_id={profile_id}, hostname={hostname} is: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factor))
+ return load_factor
+
+ else:
+ raw_output = ""
+ logger_broker.debug('loadchecker.checkload(): sending backend={backend}, profile_id={profile_id} to load checker service'.format(backend=backend, profile_id=profile_id, hostname=hostname))
+ try:
+ s.send('{backend}\r{profile_id}\r\n'.format(backend=backend, profile_id=profile_id))
+ raw_output = s.recv(1024)
+ s.close()
+ except socket.error, e:
+ logger_broker.error('loadchecker.checkload(): failure when sending data to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e)))
+
+ load_factors = {}
+ items = raw_output.split('\n')
+ for item in items:
+ if ":" in item:
+ key, val = item.split(':', 1)
+ load_factors[key] = val
+
+ logger_broker.info('loadchecker.checkload(): load metrics for backend={backend}, profile_id={profile_id} are: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factors))
+ return load_factors
+
+
+class LoadChecker(threading.Thread):
+
+ def __init__(self, config_file=None, config_defaults=None, logger=None, **kwargs):
+ """\
+ Initialize a new LoadChecker instance for querying remote X2Go Broker Agent instances
+ about server/system load, CPU usage, etc.
+
+ """
+ self.logger = logger
+
+ self.config_file = config_file
+ if self.config_file is None: self.config_file = x2gobroker.defaults.X2GOBROKER_CONFIG
+ self.config_defaults = config_defaults
+ if self.config_defaults is None: self.config_defaults = x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS
+ self.kwargs = kwargs
+
+ threading.Thread.__init__(self, target=self.loadchecker)
+ self.server_load = {}
+ self.keep_alive = True
+ self.daemon = True
+
+ def get_server_load(self, backend, profile_id, hostname):
+ """\
+ Retrieve system load for a given server (via broker backend,
+ profile ID and hostname).
+
+ @param backend: broker backend to query.
+ @type backend: C{unicode}
+ @param profile_id: profile ID of the session profile to query
+ @type profile_id: C{unicode}
+ @param hostname: hostname of the X2Go Server
+ @type hostname: C{unicode}
+
+ @return: load factor of the given server (or None if an error occurs)
+ @rtype: C{int}
+
+ """
+ try:
+ return self.server_load[backend][profile_id][hostname]
+ except KeyError:
+ return None
+
+ def get_profile_load(self, backend, profile_id):
+ """\
+ Retrieve system load for all servers for a given profile ID (and a given
+ broker backend).
+
+ @param backend: broker backend to query.
+ @type backend: C{unicode}
+ @param profile_id: profile ID of the session profile to query
+ @type profile_id: C{unicode}
+
+ @return: load factor of the given server (or None if an error occurs)
+ @rtype: C{dict}
+
+ """
+ try:
+ return self.server_load[backend][profile_id]
+ except KeyError:
+ return None
+
+ def loadchecker(self):
+ """\
+ This is the actual thread runner that queries configured / available X2Go Broker Agents in regular
+ intervals about system load, CPU types and usage.
+
+ """
+ time_to_sleep = 0
+ while self.keep_alive:
+
+ # in every loop we re-read the main configuration file(s)
+ # this allows changes to the config files to take effect immediately...
+
+ self.config = x2gobroker.config.X2GoBrokerConfigFile(config_files=self.config_file, defaults=self.config_defaults)
+ self.load_checker_intervals = self.config.get_value('global', 'load-checker-intervals')
+
+ self.broker_backends = [ "_".join(bs.split('_')[1:]) for bs in self.config.list_sections() if bs.startswith('broker_') and self.config.get_value(bs, 'enable') ]
+
+ # potentially, the X2Go Session Broker can manage different broker backends at the same time, so we initialize
+ # all configured/enabled broker backends
+ self.brokers = {}
+ num_queries = 0
+ for backend in self.broker_backends:
+
+ if self.logger: self.logger.debug('LoadChecker.loadchecker(): load checker thread waking up...')
+
+ if not self.server_load.has_key(backend):
+ self.server_load[backend] = {}
+
+ _broker_backend_module = None
+ exec("import x2gobroker.brokers.{backend}_broker as _broker_backend_module".format(backend=backend))
+ self.brokers[backend] = _broker_backend_module.X2GoBroker(config_file=self.config_file, config_defaults=self.config_defaults, **self.kwargs)
+ profile_ids_to_check = [ id for id in self.brokers[backend].get_profile_ids() if self.brokers[backend].use_load_checker(id) ]
+
+ for profile_id in profile_ids_to_check:
+
+ if not self.server_load[backend].has_key(profile_id):
+ self.server_load[backend][profile_id] = {}
+ remote_agents = self.brokers[backend].get_all_remote_agents(profile_id)
+ for remote_agent in remote_agents:
+ self.server_load[backend][profile_id][remote_agent[u'hostname']] = x2gobroker.agent.checkload(remote_agent)
+ if self.logger: self.logger.info('LoadChecker.loadchecker(): contacted remote broker agent for backend={backend}, profile_id={profile_id}, hostname={hostname}, new load factor is: {lf}'.format(backend=backend, profile_id=profile_id, hostname=remote_agent[u'hostname'], lf=self.server_load[backend][profile_id][remote_agent[u'hostname']]))
+ num_queries += 1
+ if time_to_sleep > 0:
+ if self.logger: self.logger.debug('LoadChecker.loadchecker(): sleeping for {secs}secs before querying next server'.format(secs=time_to_sleep))
+ time.sleep(time_to_sleep)
+
+ # clean up vanished hostnames
+ _hostnames = self.server_load[backend][profile_id].keys()
+ for hostname in _hostnames:
+ if hostname not in [ ra[u'hostname'] for ra in remote_agents ]:
+ del self.server_load[backend][profile_id][hostname]
+
+ # clean up vanished profile IDs
+ _profile_ids = copy.deepcopy(self.server_load[backend].keys())
+ for profile_id in _profile_ids:
+ if profile_id not in profile_ids_to_check:
+ del self.server_load[backend][profile_id]
+
+ # clean up vanished backends
+ _backends = copy.deepcopy(self.server_load.keys())
+ for backend in _backends:
+ if backend not in self.broker_backends:
+ del self.server_load[backend]
+
+ # don't do all queries every 300-or-so seconds, but distribute next round of queries over the
+ # complete load_checker_intervals range
+ if time_to_sleep == 0:
+ if self.logger: self.logger.debug('LoadChecker.loadchecker(): sleeping for {secs}secs before starting next query cycle'.format(secs=self.load_checker_intervals))
+ time.sleep(self.load_checker_intervals)
+ if num_queries > 0:
+ if time_to_sleep > 0:
+ if self.logger: self.logger.debug('LoadChecker.loadchecker(): performed {num} queries, sleeping for {secs}secs before starting next query cycle'.format(num=num_queries, secs=self.load_checker_intervals - time_to_sleep * num_queries))
+ time.sleep(self.load_checker_intervals - time_to_sleep * num_queries)
+ time_to_sleep = self.load_checker_intervals / (num_queries +1)
+ else:
+ if num_queries == 0:
+ if self.logger: self.logger.warning('LoadChecker.loadchecker(): performed {num} queries in this cycle, if this message keeps repeating itself, consider disabling the X2Go Broker Load Checker daemon'.format(num=num_queries, secs=self.load_checker_intervals - time_to_sleep * num_queries))
+ time_to_sleep = 0
+
+ def stop_thread(self):
+ self.keep_alive = False
--
Alioth's /srv/git/code.x2go.org/x2gobroker.git//..//_hooks_/post-receive-email on /srv/git/code.x2go.org/x2gobroker.git
More information about the x2go-commits
mailing list