Loading...

January 31, 2011

Install Hudson JNLP Slave Agent on Ubuntu

In this blog post we learn how to install a Hudson JNLP slave agent that is started headlessly as a service on Ubuntu. There are many tutorials and how-to's available but for me this was the easiest way to install the Hudson slave agent. We use the default Hudson installation for Ubuntu and use the provided scripts as templates for the Hudson slave scripts.

First we start with installing Hudson following the steps from Hudson Debian packages:

$ wget -q -O - http://pkg.hudson-labs.org/debian/hudson-labs.org.key | sudo apt-key add -

Next we must open /etc/apt/sources.list and add the following line:

deb http://pkg.hudson-labs.org/debian binary/

Next we can update apt-get and install Hudson using apt-get:

$ sudo apt-get update
$ sudo apt-get install hudson

After the installation via apt-get a Hudson user account is created and the several directories used by Hudson, like /usr/share/hudson and /var/lib/hudson. Now it is time to create the init scripts so the Hudson JNLP slave agent is started and stopped with scripts. We have to copy the files /etc/default/hudson and /etc/init.d/hudson to hudson-slave variants:

$ sudo cp /etc/default/hudson /etc/default/hudson-slave
$ sudo cp /etc/init.d/hudson /etc/init.d/hudson-slave

Now we can edit these files and change the contents. It is important to notice we must define a HUDSON_MASTER variable that references the Hudson master server. Also we must define a HUDSON_SLAVE variable that contains the node name as defined at the Hudson master configuration.

# /etc/default/hudson-slave
# defaults for hudson continuous integration server slave agent

# pulled in from the init script; makes things easier.
NAME=hudson-slave

# location of java
JAVA=/usr/bin/java

# arguments to pass to java
#JAVA_ARGS="-Xmx256m"

PIDFILE=/var/run/hudson/hudson-slave.pid

# user id to be invoked as (otherwise will run as root; not wise!)
HUDSON_USER=hudson

# location of the hudson jar file
HUDSON_JAR=/usr/share/hudson/hudson.jar

# hudson home location
HUDSON_HOME=/var/lib/hudson

# log location.  this may be a syslog facility.priority
HUDSON_LOG=/var/log/hudson/$NAME.log
#HUDSON_LOG=daemon.info

# OS LIMITS SETUP
#   comment this out to observe /etc/security/limits.conf
#   this is on by default because http://github.com/feniix/hudson/commit/d13c08ea8f5a3fa730ba174305e6429b74853927
#   reported that Ubuntu's PAM configuration doesn't include pam_limits.so, and as a result the # of file
#   descriptors are forced to 1024 regardless of /etc/security/limits.conf
MAXOPENFILES=8192

# server name of hudson
HUDSON_MASTER=hudson-master

# name of hudson slave
HUDSON_SLAVE=hudson-slave-01

# arguments to pass to hudson.
HUDSON_ARGS="-jnlpUrl http://$HUDSON_MASTER/computer/$HUDSON_SLAVE/slave-agent.jnlp"

In the init script we don't change much compared to the original script. In the do_start() method we use the wget command to get the slave.jar from the Hudson master, so we always use an up-to-date version if the script is started.

#!/bin/bash
# /etc/init.d/hudson-slave
# debian-compatible hudson slave agent startup script.
# Amelia A Lewis <alewis@ibco.com>
#
### BEGIN INIT INFO
# Provides:          hudson-slave
# Required-Start:    $remote_fs $syslog $network
# Required-Stop:     $remote_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start hudson slave agent at boot time
# Description:       Controls the hudson continuous integration slave agent.
### END INIT INFO

PATH=/bin:/usr/bin:/sbin:/usr/sbin

DESC="Hudson Continuous Integration Server Slave Agent"
NAME=hudson-slave
SCRIPTNAME=/etc/init.d/$NAME

[ -r /etc/default/$NAME ] && . /etc/default/$NAME

#DAEMON=$HUDSON_SH
DAEMON=/usr/bin/daemon
DAEMON_ARGS="--name=$NAME --inherit --env=HUDSON_HOME=$HUDSON_HOME --output=$HUDSON_LOG --pidfile=$PIDFILE"

SU=/bin/su

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# load environments
if [ -r /etc/default/locale ]; then
  . /etc/default/locale
  export LANG LANGUAGE
elif [ -r /etc/environment ]; then
  . /etc/environment
  export LANG LANGUAGE
fi

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

# Make sure we run as root, since setting the max open files through
# ulimit requires root access
if [ `id -u` -ne 0 ]; then
    echo "The $NAME init script can only be run as root"
    exit 1
fi

#
# Function that starts the daemon/service
#
do_start()
{
    # get slave.jar from server
    wget -O $HUDSON_JAR http://$HUDSON_MASTER/jnlpJars/slave.jar > /dev/null 2>&1

    # the default location is /var/run/hudson/hudson.pid but the parent directory needs to be created
    mkdir `dirname $PIDFILE` > /dev/null 2>&1 || true
    chown $HUDSON_USER `dirname $PIDFILE`
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    $DAEMON $DAEMON_ARGS --running && return 1

    # If the var MAXOPENFILES is enabled in /etc/default/hudson then set the max open files to the
    # proper value
    if [ -n "$MAXOPENFILES" ]; then
        [ "$VERBOSE" != no ] && echo Setting up max open files limit to $MAXOPENFILES
        ulimit -n $MAXOPENFILES
    fi

    # --user in daemon doesn't prepare environment variables like HOME, USER, LOGNAME or USERNAME,
    # so we let su do so for us now
    $SU -l $HUDSON_USER --shell=/bin/bash -c "$DAEMON $DAEMON_ARGS -- $JAVA $JAVA_ARGS -jar $HUDSON_JAR $HUDSON_ARGS" || return 2
}


#
# Verify that all hudson processes have been shutdown
# and if not, then do killall for them
#
get_running()
{
    return `ps -U $HUDSON_USER --no-headers -f | egrep -e '(java|daemon)' | grep -c . `
}

force_stop()
{
    get_running
    if [ $? -ne 0 ]; then
 killall -u $HUDSON_USER java daemon || return 3
    fi
}

# Get the status of the daemon process
get_daemon_status()
{
    $DAEMON $DAEMON_ARGS --running || return 1
}


#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    get_daemon_status
    case "$?" in
 0)
     $DAEMON $DAEMON_ARGS --stop || return 2
        # wait for the process to really terminate
        while true;
        do
            sleep 1
            $DAEMON $DAEMON_ARGS --running || break
        done
     ;;
 *)
     force_stop || return 3
     ;;
    esac

    # Many daemons don't delete their pidfiles when they exit.
    rm -f $PIDFILE
    return 0
}

case "$1" in
  start)
    log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) log_end_msg 0 ;;
        2) log_end_msg 1 ;;
    esac
    ;;
  stop)
    log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) log_end_msg 0 ;;
        2) log_end_msg 1 ;;
    esac
    ;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
          0) log_end_msg 0 ;;
          1) log_end_msg 1 ;; # Old process is still running
          *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
   # Failed to stop
 log_end_msg 1
 ;;
    esac
    ;;
  status)
      get_daemon_status
      case "$?" in
  0) echo "Hudson slave agent is running with the pid `cat $PIDFILE`";;
         *)
       get_running
       procs=$?
       if [ $procs -eq 0 ]; then
    echo -n "Hudson slave agent is not running"
    if [ -f $PIDFILE ]; then
        echo ", but the pidfile ($PIDFILE) still exists"
    else
        echo
    fi

       else
    echo "$procs instances of hudson slaves are running at the moment"
    echo "but the pidfile $PIDFILE is missing"
       fi
       ;;
      esac
    ;;
  *)
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    exit 3
    ;;
esac

exit 0

We are almost done. We only have to configure the correct runlevels for our init script and disable the hudson scripts that were installed by the apt-get installation:

$ sudo /etc/init.d/hudson stop
$ sudo update-rc.d -f hudson remove
$ sudo update-rc.d hudson-slave defaults

After we have configured the slave node in the Hudson master configuration we can start our Hudson JNLP slave agent:

$ sudo /etc/init.d/hudson-slave start

To change environment variables for the Hudson build processes we only have to create or modify the settings for the Hudson user. For example we can add a GRAILS_HOME environment variable to the .profile file in /var/lib/hudson and it is available in the build process.