[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