Load balancing

This is a extension of what is described in http://wiki.debian.org/DebianEdu/Documentation/Etch/HowTo/NetworkClients#head-7587290321a833444df956e05b30f1a4cde2df3e - it extends part 2 with a second option, where the choosen LTSP server is not random but the least utilized one. Un

Option 2

The second option is more advanced, it gives you a server list generated by querying ldminfod for the server rating. By querying ldminfod, you can get the current rating state of the server. This rating goes from 0 to 100, higher is better. For this to work, you have to use the ubuntu version of ldminfod. (remark from h01ger: which version of ubuntu? it looks like Debian lenny is enough too?) The Skolelinux version doesn't have the server rating function.

First you should make a backup of the original ldminfod-file, which is in /usr/sbin/. Then, put the following script in a new ldminfod-file. Make sure the new file has the same permissions as the old one.

import sys
import os
import locale
from subprocess import *
def get_memory_usage():
    memusg = {}
    # Get memory usage information, according to /proc/meminfo
    f = open('/proc/meminfo', 'r')
    swap_free = 0
    mem_physical_free = 0
    mem_buffers = 0
    mem_cached = 0
    for line in f.readlines():
        tokens = line.split()
        label = tokens[0]
        size = tokens[1]
        try:
            size = int(size)
        except:
            # The line is an header, skip it.
            continue
        # We approximate kb to bytes
        size = size * 1024
        if label == 'MemTotal:':
            memusg['ram_total'] = size
        elif label == 'MemFree:':
            mem_physical_free = size
        elif label == 'Buffers:':
            mem_buffers = size
        elif label == 'Cached:':
            mem_cached = size
        elif label == 'SwapTotal:':
            memusg['swap_total'] = size
        elif label == 'SwapFree:':
            swap_free = size
    f.close()
    memusg['ram_used'] = memusg['ram_total'] - mem_physical_free - mem_buffers - mem_cached
    memusg['swap_used'] = memusg['swap_total'] - swap_free
    return memusg
def get_load_average():
    # Gets the current system load, according to /proc/loadavg
    loadavg = {}
    load_file = open('/proc/loadavg')
    load_infos = load_file.read().split()
    loadavg['one_min_avg'] = load_infos[0]
    loadavg['five_min_avg'] = load_infos[1]
    loadavg['fifteen_min_avg'] = load_infos[2]
    # scheduling_info = load_infos[3] # not used
    # last_pid = load_infos[4]
    load_file.close()
    return loadavg
def compute_server_rating():
    """Compute the server rating from it's state
       The rating is computed by using load average and the memory
       used. The returned value is betweed 0 and 100, higher is better
    """
    max_acceptable_load_avg = 8.0
    mem = get_memory_usage()
    load = get_load_average()
    rating = 100 - int(
        50 * ( float(load['fifteen_min_avg']) / max_acceptable_load_avg ) +
        50 * ( float(mem['ram_used']) / float(mem['ram_total']) )
        )
    if rating < 0:
        rating = 0
    return rating
def get_sessions (dir):
    """Get a list of available sessions.
       Returns a list of sessions gathered from .desktop files
    """
    sessions = []
    if os.path.isdir(dir):
        for f in os.listdir(dir):
            if f.endswith('.desktop') and os.path.isfile(os.path.join(dir, f)):
                x={}
                for line in file(os.path.join(dir, f), 'r').readlines():
                    if 'Exec=' in line:
                        variable, value = line.split('=')
                        x[variable]=value
                if 'TryExec' in x:
                    sessions.append(x['TryExec'].rstrip())
                elif 'Exec' in x:
                    sessions.append(x['Exec'].rstrip())
    return sessions
if __name__ == "__main__":
    # Get the server's default locale
    # We want it to appear first in the list
    try:
        lines = Popen(['locale'], stdout=PIPE).communicate()[0]
    except OSError:
        print "ERROR: failed to run locale"
        sys.exit(0)
    for line in lines.split():
        if line.startswith('LANG='):
            defaultlocale = line.split('=')[1].strip('"')
    defaultlocale = defaultlocale.replace('UTF8', 'UTF-8')
    print "language:" + defaultlocale
    # Get list of valid locales from locale -a
    try:
        lines = Popen(['locale', '-a'], stdout=PIPE).communicate()[0]
    except OSError:
        print "ERROR"
        sys.exit(0)
    langs = lines.split(None)
    langs.sort()
    for lang in langs:
        lang = lang.rstrip()
        if lang.endswith('.utf8'):
            # locale returns .utf8 when we want .UTF-8
            lang = lang.replace('.utf8','.UTF-8')
        else:
            # if locale did not end with .utf8, do not add to list
            continue
        if lang != 'POSIX' and lang != 'C' and lang != defaultlocale:
            print "language:" + lang
    try:
        lines = get_sessions('/usr/share/xsessions/')
    except OSError:
        print "ERROR"
        sys.exit(0)
    for line in lines:
        print "session:" + line
    # Get the rating of this server
    rate = 0
    try:
        rate = compute_server_rating()
    except:
        print "ERROR"
        sys.exit(0)
    print "rating:" + str(compute_server_rating())

Now you have to edit "/opt/ltsp/i386/etc/lts.conf" and add this:

MY_SERVERS = "xxxx xxxx xxxx"

Replace xxxx with either the IP or hostname of the servers, list must be space separated. Then, put the following script in /opt/ltsp/i386/usr/lib/ltsp/get_hosts on the server you chose to be the loadbalancing server.

LIST=""
# Load query on all servers
for i in $MY_SERVERS; do
    LOAD=`nc $i 9571|grep rating|cut -d: -f2`
    if test $LOAD; then
        # add to the list
        LIST="$LIST$LOAD $i\n"
    fi
done
# Now LIST contains the list of servers sorted by load
LIST=`echo -e $LIST | sort -nr`
# Check if the 1st items have the same load. If so, randomly choose one.
OLDIFS=$IFS
IFS="
"
BESTLIST=( )
I=0
for LINE in $LIST ;do
    LOAD=`echo $LINE|cut -f 1 -d " "`
    if [ "$OLDLOAD" -a "$LOAD" != "$OLDLOAD" ] ;then
        break
    fi
    BESTLIST[$I]="$LINE"
    OLDLOAD=$LOAD
    I=$((I+1))
done
RAND=$(( $RANDOM % $I ))
# print the choosen host
echo ${BESTLIST[$RAND]} | cut -f 2 -d " "
exit 0