[X2Go-Dev] [X2Go-Commits] [x2gobroker] 05/08: New feature: x2gobroker-loadchecker daemon.

Mike Gabriel mike.gabriel at das-netzwerkteam.de
Thu Mar 26 11:50:57 CET 2015


Hi all,

I just pushed a new feature to the X2Go Session Broker Git repo  
(master branch).

This feature is still work in progress, but for multi-site testing, I  
need the packages from the nightly builds repo.

So, stay tuned...

Mike


On  Do 26 Mär 2015 11:47:54 CET, git-admin wrote:

> 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
> _______________________________________________
> x2go-commits mailing list
> x2go-commits at lists.x2go.org
> http://lists.x2go.org/listinfo/x2go-commits


-- 

DAS-NETZWERKTEAM
mike gabriel, herweg 7, 24357 fleckeby
fon: +49 (1520) 1976 148

GnuPG Key ID 0x25771B31
mail: mike.gabriel at das-netzwerkteam.de, http://das-netzwerkteam.de

freeBusy:
https://mail.das-netzwerkteam.de/freebusy/m.gabriel%40das-netzwerkteam.de.xfb
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: Digitale PGP-Signatur
URL: <http://lists.x2go.org/pipermail/x2go-dev/attachments/20150326/f7cc0bb8/attachment-0001.pgp>


More information about the x2go-dev mailing list