Scripted appserver war deployment example

From ControlTier

Jump to: navigation, search

Requires Version 3.4.5 (help?)

This example shows how the ControlTier framework can provide a useful set of features that can make the scripting you create for application deployment simpler, more flexible, and ultimately more accessible. It uses the script-driven approach and strategies from the Scripters cookbook.

This document shows the basics of using ctl-exec, coreutils, and the use of a WebDAV repository to deploy a Tomcat application server, and a simple webapp called "simple". This deployment will be referred to as "simpleTomcat" and the deployment process for that application will be done via shell script named "simpleTomcat.sh".

This document walks through several iterations of the simpleTomcat.sh script implementation showing how the script is progressively made more flexible and reusable by taking advantage of ControlTier features and other best practices. Along the way, you will also see how these examples adhere to essential ControlTier Concepts.


Contents

Dependencies

This example has these dependencies.

  • ControlTier — 3.4.5
  • Unix
    • This example is not compatible with Windows. It is compatible with Linux, Mac OS X and most unix variants.
  • "demo" project
    • Be sure the "demo" project has been created across all nodes you will run this example
  • Tomcat — 5.5.28

Note: A WAR file is included in the $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs directory so there is no external dependency for that.

Scripting with ctl-exec

The ctl-exec command provides a convenient method to execute a local script file across a set of hosts. In this mode, ctl-exec first copies the script to the remote host and then executes it. The ctl-exec command supports several other useful features for executing commands across a set of hosts including: filtering logic, parallelism, and error handling.

Rather than require you to name each host explicitly, the ctl-exec command lets you dispatch to a set of nodes through a filter-based lookup option. Each node registered to a ControlTier project includes a set of standard metadata that you can use in your filter specification. The examples discussed here will use tags to execute a script to just the nodes that share that tag name. Tags are useful to create a symbolic name for a set of nodes that share a characteristic, like a particular application they might all have resident.

You can run any script from the ControlTier server host across a set of tagged nodes using ctl-exec with this usage format:

ctl-exec -p project -I tags=tagname -s script

Letting ctl-exec do the dispatching for you, reduces complexity from the script writing. With ctl-exec as a front end, you avoid writing iteration and remote command dispatch logic from your scripts. This lets you write your scripts so that they always assume a local execution mode and thus easier to trouble shoot.

In the examples that follow, nodes where the "simple" application and Tomcat deployed are referred to as "simpleTomcat". This means anytime ctl-exec is used to call commands against nodes with "simpleTomcat" just those nodes are selected.

The diagram below illustrates ctl-exec executing the "simpleTomcat.sh" script to the nodes tagged "simpleTomcat":

Image:example-ctl-exec-scripts-screenshot.png

The specific command usage to support the scenario shown in the diagram above is shown here: ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh

Scripting with WebDAV

Deployment procedures typically involve the distribution of various kinds of files to the nodes where the deployments reside. These files may be packaged artifacts that come from an internal build process. They may be 3rd party software packages. Configuration files may also need to be distributed. Often times these files are staged to a central administrative host and either pushed out via SCP. Alternatively, a web server might be used to make files accessible by HTTP clients such as wget or curl to pull them where needed.

Since file distribution is so essential to managing deployment, the ControlTier server includes a WebDAV server. WebDAV is a set of extensions to HTTP that make web content read/write. WebDAV provides a convenient way to loading the repository with files your deployment procedures need and letting you access them via any HTTP client.

In the examples that follow, you will see how the WebDAV facility is leveraged via commands from the coreutils library. In these examples, Zip and War files that are to deployed are first staged in the WebDAV and later pulled down.

The four deployment steps

In each iteration of the deployment scripting, you will see how the deployment process is always carried out in four main steps:

  1. Shutdown: Stop the server (tomcat)
    • Check if it is running and if so, run the tomcat shutdown.sh script
  2. Installation: Install packages
    • Download and extract the Tomcat Zip and Simple WAR files
  3. Configuration: Configure the server (tomcat)
    • Customize the tomcat server.xml to run on different ports. Change the file permissions for the tomcat stop/start scripts to be executable
  4. Startup: Start the server (tomcat)
    • Run the tomcat startup.sh script. Verify it is running by checking the listening port.

Any time the deployment process is performed, these same four steps will be executed.

Iterative approach

The example is broken down into several development iterations. During each iteration, the "simpleTomcat.sh" deployment script is re-implemented to take advantage of ControlTier features to make it more flexible, reusable and maintainable.

The table below summarizes the goal to meet for each iteration.

IterationImplementation changes
0Original script (no change)
1Eliminate SSH calls and handcoded for loop with ctl-exec. Replace use of scp to push files with pull-based model supported by WebDAV
2Introduce utility modules and decompose single script into a set of simpler but separate scripts accessible from WebDAV
3Expose individual steps as a set of sub commands controlled through command line option

Iteration Zero

We begin with a scripting approach that assumes no ControlTier use and is a typical example for executing a process like the one for the simple Tomcat deployment. The basic requirement is to execute #The four deployment steps across all the nodes where we want the "simple" Tomcat webapp deployed. It's inconvenient and time consuming to login to each machine to run these steps, so there is an additional requirement that it be done from a central administrative host over SSH. Finally, the Zip and WAR files will be staged to a central web server to make them easily distributable to anywhere on the network.

The scripts

Everything is implemented in a single script, "simpleTomcat.sh"

v0/simpleTomcat.sh

Here's the implementation of the simpleTomcat.sh that uses no ControlTier functionality. It's been organized by comments to show the for main steps. The script has been designed so each step is done across the nodes before the next step is performed. The basic code structure is shown below. You can see it's implemented using the for shell builtin well known in Unix shell. The set of nodes to execute the steps are specified by the user and referenced inside the script as $NODES. Inside the for loop are one or more calls to SSH to execute the needed command:

for node in $NODES; do
  ssh $node command1
  ...
  ssh $node another command
  ... and so on
done

At the top of the script are a few shell variables to specify the URL to get packages and specify package names, versions and install directory.

Here's how the "simpleTomcat.sh" implements the #The four deployment steps :

  1. Step 1: Start tomcat
    • Remotely execute the netstat and grep commands to check if the Tomcat server is running and hence listening on its port. If it is, then stop it by ssh'ing the tomcat CATALINA_HOME/bin/shutdown.sh script.
  2. Step 2: Install packages
    • Remotely execute curl to get files from the WebDAV repository. Then ssh unzip to extract each archive into the specified directory.
  3. Step 3: Configure tomcat
    • Remotely execute sed to substitute port settings in the server.xml. Then use ssh to chmod the files in CATALINA_HOME/bin/* to set their execute bit.
  4. Step 4: Start tomcat
    • SSh the CATALINA_HOME/bin/startup.sh script to start Tomcat. Verify that the Tomcat server comes up by using SSh to call netstat and grep to check the process is bound to its port.

Additionally, each step is demarcated with a set of messages emitted by the echo command. These will show begin/end messages to make it easier to follow progress when watching the output.

#!/bin/sh
# File: v0/simpleTomcat.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
 
[ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; }
 
USAGE="simpleTomcat.sh <node> <node> <node>"
[ "$#" -lt 1 ] && { die "Usage: $USAGE" ; }
 
# Nodes where simple webapp Tomcat deployement reside
NODES="$@"
 
# The zip and war files reside inside a WebDAV
REPO_URL=http://strongbad:8080/jackrabbit/repository/workbench
TOMCAT_PKGBASE=apache-tomcat-5.5.28
SIMPLE_PKGBASE=simple
INSTALL_DIR=ctier/pkgs
export CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE
export CATALINA_BASE=$CATALINA_HOME;
 
echo "Deploy simple tomcat. Steps: stop, install, configure, start"
#
# Step 1: Stop tomcat
#
for node in $NODES; do
    echo "begin stop (1/4) ..."
    ssh $node netstat -an|grep 28080|grep -q LISTEN && {
	echo "Tomcat listening (port=28080). Running stop script ..."
	ssh $node $CATALINA_HOME/bin/shutdown.sh || die "Script failed: stop.sh"
    }
    echo "end stop (1/4)"
done
 
#
# Step 2: Install Packages 
#
for node in $NODES; do
    echo "begin install (2/4) ..."
    scp $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs/$TOMCAT_PKGBASE.zip \
	$node:$INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip"
    ssh $node test -d $CATALINA_HOME && {
	echo "removing old CATALINA_HOME"
	ssh $node rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment"
    } 
 
    ssh $node "cd $INSTALL_DIR; unzip -q $TOMCAT_PKGBASE.zip" || die "Extract failed: $TOMCAT_PKGBASE.zip"
 
    ssh $node mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || {
	die "failed making $SIMPLE_PKGBASE directory"
    }
    scp $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs/$SIMPLE_PKGBASE.war \
	$node:$CATALINA_HOME/webapps/$SIMPLE_PKGBASE/$SIMPLE_PKGBASE.war || {
	die "Download failed: $SIMPLE_PKGBASE.war"
    }
# extract the WAR
    ssh $node "cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE; unzip -q $SIMPLE_PKGBASE.war" || {
	die "Extract failed: $SIMPLE_PKGBASE.war"
    }
    ssh $node rm  $CATALINA_HOME/webapps/$SIMPLE_PKGBASE/$SIMPLE_PKGBASE.war 
    echo "end install (2/4)"
done
#
# Step 3: Configure tomcat 
#
for node in $NODES; do
    echo "begin configure (3/4) ..."
    echo "Chmod'ing files in $CATALINA_BASE/bin/*.sh"
    ssh $node chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed"
 
    echo "Customizing $CATALINA_BASE/conf/server.xml ..."
 
    ssh $node "sed 's/8080/28080/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml"
    ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || {
	die "Text replace failed: $CATALINA_BASE/conf/server.xml"
    }
    ssh $node "sed 's/8005/28005/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml"
    ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || {
	die "Text replace failed: $CATALINA_BASE/conf/server.xml"
    }
    ssh $node "sed 's/8009/28009/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml"
    ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || {
	die "Text replace failed: $CATALINA_BASE/conf/server.xml"
    }
    echo "$CATALINA_BASE/conf/server.xml customized"
    echo "end configure (3/4)"
done
#
# Step 4: Start tomcat 
#
for node in $NODES; do
    echo "begin start (4/4) ..."
    echo "Invoking $CATALINA_HOME/bin/startup.sh"
    ssh $node $CATALINA_HOME/bin/startup.sh || die "Script failed: start.sh"
# verify it is running
    sleep 10; # Wait 10 seconds to give Tomcat time to start and be read
    ssh $node netstat -an|grep 28080|grep -q LISTEN || die "Status failed: Tomcat not listening: (port=28080)"
    echo "end start (4/4)"
    echo "Deploy simple tomcat completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE"
done
 
# end v0/simpleTomcat.sh

Running

v0/simpleTomcat.sh

From the admin host run the simpleTomcat.sh script specifying the nodes you want to deploy simple tomcat:

execute:

cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts

Change directory to the "scripts" subdirectory of the scripted-appserver-war-deployment example

$ ./v0/simpleTomcat.sh centos2

You should see output resembling the messages shown here:

output:

Deploy simple tomcat. Steps: stop, install, configure, start
begin stop (1/4) ...
Tomcat listening (port=28080). Running stop script ...
end stop (1/4)
begin install (2/4) ...
removing old CATALINA_HOME
end install (2/4)
begin configure (3/4) ...
Chmod'ing files in ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh
Customizing ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ...
ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized
end configure (3/4)
begin start (4/4) ...
Invoking ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh
end start (4/4)
Deploy simple tomcat completed. Visit: http://strongbad.local:28080/simple

Once the script completes you should be able to access the deployed Tomcat and "simple" application. Visit the link like so (eg, http://centos2:28080/simple/):

Image:simple-tomcat-webapp-screenshot.png

You should see the current date and time displayed in the browser page.

Observations

The v0/simpleTomcat.sh script was sufficient for the task of managing the steps in the deployment process. There are several observations worth noting:

  1. Remote command execution (a form of dispatching) and individual deployment tasks are intermingled
  2. Due to #1, harder to debug because can't run the procedure locally on just one node
  3. Evaluating conditions is tricking when using SSH. Relies on non-zero SSH result code processing
  4. The "LISTEN" status used by the netstat/grep pipeline doesn't really say if the server is responsive or not
  5. Must be conscious of quoting when executing multipart command strings over SSH (eg, when having to do things in relative directories)
  6. Uses scp to push files from admin host. Push-mode for file distribution can sometimes be a scaling bottleneck.

Iteration One

Let's refactor the simpleTomcat.sh script to address some of the shortcomings mentioned above. The main approach here will be to:

  • Separate the SSH logic from the functional steps by introducing ctl-exec
    • Make all the steps work more atomically
    • Enable easier debugging
  • Use the WebDAV server to facilitate a pull-model for file distribution.
    • Lets HTTP clients pull the artifacts where needed

The scripts

In this iteration you will see the implementation has changed to eliminate all calls to the ssh command for remote execution. Instead the script will assume to be executed via ctl-exec.

v1/simpleTomcat.sh

As in #v0/simpleTomcat.sh the script implements the four steps mentioned in the overview like so except this time all the steps are executed locally.

File listing: v1/simpleTomcat.sh

#!/bin/sh
# File: v1/simpleTomcat.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
 
[ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; }
 
DAV_URL=http://strongbad:8080/jackrabbit/repository/workbench
TOMCAT_PKGBASE=apache-tomcat-5.5.28
SIMPLE_PKGBASE=simple
INSTALL_DIR=$CTIER_ROOT/pkgs
export CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE
export CATALINA_BASE=$CATALINA_HOME;
 
echo "Deploy simple tomcat. Steps: stop, install, configure, start"
#
# Step 1: Ensure tomcat down if running 
#
echo "begin stop (1/4) ..."
netstat -an|grep 28080|grep -q LISTEN && {
    echo "Tomcat listening (port=28080). Running stop script ..."
    $CATALINA_HOME/bin/shutdown.sh || die "Script failed: stop.sh"
}
echo "end stop (1/4)"
 
#
# Step 2: Install Packages 
#
echo "begin install (2/4) ..."
curl -s --user default:default $DAV_URL/pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \
    -o $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip"
 
[ -d $CATALINA_HOME ] && {
    rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment"
}
cd $INSTALL_DIR
unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip"
curl -s --user default:default $DAV_URL/pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \
    -o  $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war"
 
# extract the WAR
mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || {
    die "failed making $SIMPLE_PKGBASE directory"
}
cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE
unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war"
echo "end install (2/4)"
#
# Step 3: Configure tomcat 
#
echo "begin configure (3/4) ..."
echo "Chmod'ing files in $CATALINA_BASE/bin/*.sh"
chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed"
 
echo "Customizing $CATALINA_BASE/conf/server.xml ..."
 
sed 's/8080/28080/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml
mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
sed 's/8005/28005/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml
mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
sed 's/8009/28009/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml
mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
echo "$CATALINA_BASE/conf/server.xml customized"
echo "end configure (3/4)"
#
# Step 4: Start tomcat 
#
echo "begin start (4/4) ..."
echo "Invoking $CATALINA_HOME/bin/startup.sh"
$CATALINA_HOME/bin/startup.sh || die "Script failed: start.sh"
# verify it is running
sleep 10; # Wait 10 seconds to give Tomcat time to start and be read
netstat -an|grep 28080|grep -q LISTEN || die "Status failed: Tomcat not listening: (port=28080)"
echo "end start (4/4)"
echo "Deploy simple tomcat completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE"
 
# end v1/simpleTomcat.sh

v1/setup.sh

A setup.sh script will carry out a few steps ahead of doing the deployment. Primarily, it tags your specified Nodes with the name "simpleTomcat" and then upload the apache-tomcat-5.5.28.zip and simple.war files to the WebDAV. It makes use of the davutil module (one of the coreutils you'll learn about more later) to perform the WebDAV PUT operations. The add-tag command is used to set each node with the name "simpleTomcat".

#!/bin/sh
# Name: v1/setup.sh
# Usage: setup.sh <node> <node> <node>
die() { echo "ERROR: $@" ; exit 1 ; }

[ -d $CTIER_ROOT/examples/scripted-appserver-war-deployment ] || {
    die "Example directory not found: $CTIER_ROOT/examples/scripted-appserver-war-deployment"
}
#
# Tag the nodes
#
[ $# -gt 0 ] && {
    echo "Adding simpleTag to these nodes: [$@] ..."
    for node in $@; do
	ctl -z -p demo -m ProjectBuilder -c add-tag --\
            -type Node -name $node -tag simpleTomcat
    done
}
#
# Upload the packages
#
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs
# Ensure the Tomcat zip was downloaded.
[ -f apache-tomcat-5.5.28.zip ] || {
    die "Not found: apache-tomcat-5.5.28.zip. Go to http://tomcat.apache.org/download-55.cgi and save it here: `pwd`"
}
# Upload the Zip
ctl -z -p demo -m davutil -c put --\
   -file `pwd`/apache-tomcat-5.5.28.zip \
    -url dav://pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip || {
    die "Upload failed: apache-tomcat-5.5.28.zip" 
}
# Upload the War
ctl -z -p demo -m davutil -c put --\
   -file `pwd`/simple.war \
    -url dav://pkgs/demo/war/wars/simple.war || die "Failed uploading simple.war"

Running

Begin by running the setup and then run the deployment script.

v1/setup.sh

The setup.sh script is invoked directly.

  1. cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v1/
    • Change directory to the "v1" subdirectory
  2. sh ./setup.sh <node>
    • Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example: sh ./setup.sh centos2

The setup.sh script should emit output resembling the messages below:

Adding simpleTomcat to these nodes: [centos2] ...
Deleting: /Users/alexh/ctier/ctl/var/sessions/find-objects/add-tag.session
|
|--(Node) centos2
session data saved: /Users/alexh/ctier/ctl/var/sessions/find-objects/add-tag.session
Found [1] objects. Adding tags ...
Tagging object with tag simpleTomcat: centos2[Node] ...
Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip
Uploading: apache-tomcat-5.5.28.zip
Puted 1 file to http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip
Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war
Uploading: simple.war
Puted 1 file to http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war
Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v1
Uploading: simpleTomcat.sh
Puted 1 files to http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v1
Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war
setup.sh completed

The setup.sh tagged the nodes you specified with the name "simpleTomcat. You can see each node's tag information via a ctl-exec listing. Here we use ctl-exec -v to list information about the nodes registered in the demo project.

$ ctl-exec -p demo -v
centos2:
   hostname: centos2
   os-arch: i386
   os-family: unix
   os-name: Linux
   os-version: 2.6.9-34.EL
   tags: [simpleTomcat]
strongbad:
   hostname: strongbad
   os-arch: i386
   os-family: unix
   os-name: Mac OS X
   os-version: 10.5.6
   tags: []
ubuntu:
   hostname: ubuntu
   os-arch: i386
   os-family: unix
   os-name: Linux
   os-version: 2.6.27-7-generic
   tags: [simpleTomcat]

Below is a screen shot of Workbench's Node Manager showing information about the same nodes. Detail about "centos2" is revealed making the "simpleTomcat" tag visible.

Image:example-nodes-screenshot.png

v1/simpleTomcat.sh

Call the simpleTomcat.sh script via ctl-exec

The ctl-exec command will execute the simpleTomcat.sh script on each node that is tagged "simpleTomcat". The simpleTomcat.sh script will execute locally on each of the nodes.

execute

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh

You should see output resembling the messages shown here:

output

number of nodes to dispatch to: 1, (threadcount=1)
Connecting to centos2:22
done.
Connecting to centos2:22
cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Connecting to centos2:22
cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Deploy simple tomcat. Steps: stop, install, configure, start
begin stop (1/4) ...
end stop (1/4)
begin install (2/4) ...
end install (2/4)
begin configure (3/4) ...
Chmod'ing files in /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh
Customizing /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ...
/home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized
end configure (3/4)
begin start (4/4) ...
Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh
end start (4/4)
Deploy simple tomcat completed. Visit: http://centos2:28080/simple

Once the script completes you should be able to access the deployed Tomcat and "simple" application. Visit the link like so (eg, http://localhost:28080/simple/).

Observations

The v1/simpleTomcat.sh script was sufficient for the task of managing the steps in the deployment process. There are a couple shortcomings though:

  1. Hard-coded URL and authentication info to get the archives from the WebDAV repository. It's also a possibility that the curl utility may not be available across all hosts.
  2. The method used to check if tomcat was listening relied on a single 10 second sleep interval. It would be better to set a maximum wait time and then check periodically to see if Tomcat is running.
    • Also, relies on the unix pipeline of netstat and grep which may not behave the same way across different Unix OS variants.
  3. Some of the steps of this script could be broken out into their own files. Simpler and focussed script are often easier to read, understand and maintain.

Iteration Two

Let's refactor the simpleTomcat.sh script to address the shortcomings mentioned above. The basic refactoring strategy will be to:

  • Separate the deployment process logic from the individual deployment steps into two layers:
    • Simple step-specific scripts that perform the stop, start and configure steps
    • The simpleTomcat.sh script's role will become that of the orchestrator of the steps.
  • Introduce the use of coreutils commands to address how to:
    • access files from the repository
    • check the Tomcat listening port

The scripts

In this iteration you will see the implementation of the deployment process broken into four scripts:

  1. start.sh: Manages the startup step
  2. stop.sh: Manages the stop step
  3. configure.sh: Manages the tomcat configuration step
  4. simpleTomcat.sh: Manages the process itself and calls the above scripts at the needed step

A number of coreutils commands are also used:

  • netutil listening command checks network port status. It has options to set a maximum wait interval and will periodically retry the check
  • davutil copy command will be used to copy resources from the WebDAV to the local file system.
  • shellutil exec command can execute scripts that are stored on the WebDAV.

v2/simpleTomcat.sh

The refactored simpleTomcat.sh script now becomes a mixture of calls to commands in coreutils and calls to the new external scripts. Here's how each step is now be implemented:

  1. Step 1: Stop tomcat
    • Use the netutil listening command to check if the tomcat server is running and hence listening on 28080. If it is, then stop it by calling the stop.sh script via shellutil exec.
  2. Step 2: Install packages
    • Use the davutil copy command to copy the Zip DAV resource to the local filesystem. If a previous deployment of tomcat exists, it will remove the prior version, and then use the unzip command to extract the Zip.
    • Use the davutil copy command to copy the War DAV resource to the local filesystem. Make the CATALINA_HOME/webapps/simple directory, then use the unzip command to extract the WAR there.
  3. Step 3: Configure tomcat
    • Invoke the configure.sh script via shellutil exec
  4. Step 4: Start tomcat
    • Call the start.sh script via shellutil#exec to bring up Tomcat. It verifies the tomcat server comes up by using netutil listening to check the process is bound to the 28080 port.

Each step is demarcated with a set of messages emitted by the echo command.

File listing: v2/simpleTomcat.sh

#!/bin/sh
# File: v2/simpleTomcat.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
 
[ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; }
 
SCRIPTS_URL=dav://examples/simpleTomcat/scripts/v2
TOMCAT_PKGBASE=apache-tomcat-5.5.28
SIMPLE_PKGBASE=simple
INSTALL_DIR=$CTIER_ROOT/pkgs
CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE
 
echo "Deploy simple tomcat. Steps: stop, install, configure, start"
 
#
# Step 1: Stop tomcat
#
echo "begin stop (1/4) ..."
 
listening=`ctl -z -p demo -m netutil -c listening -- -port 28080`
[ "$listening" = "true" ] && {
    echo "Tomcat listening (port=28080). Running stop script ..."
    ctl -z -p demo -m shellutil -c exec --\
        -executable /bin/sh -scripturl $SCRIPTS_URL/stop.sh \
	-argline $CATALINA_HOME || die "Script failed: stop.sh"
}
echo "end stop (1/4)"
 
#
# Step 2: Install packages 
#
echo "begin install (2/4) ..."
ctl -z -p demo -m davutil -c copy --\
    -src dav://pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \
    -dest $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip"
[ -d $CATALINA_HOME ] && {
    rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment"
}
cd $INSTALL_DIR
unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip"
 
ctl -z -p demo -m davutil -c copy --\
    -src dav://pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \
    -dest $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war"
# extract the WAR
mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || {
    die "failed making $SIMPLE_PKGBASE directory"
}
cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE
unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war"
echo "end install (2/4)"
 
#
# Step 3: Configure tomcat 
#
echo "begin configure (3/4) ..."
ctl -z -p demo -m shellutil -c exec --\
    -executable /bin/sh -scripturl $SCRIPTS_URL/configure.sh \
    -argline $CATALINA_HOME || die "Script failed: configure.sh"
echo "end configure (3/4)"
 
#
# Step 4: Start tomcat 
#
echo "begin start (4/4) ..."
 
ctl -z -p demo -m shellutil -c exec --\
    -executable /bin/sh -scripturl $SCRIPTS_URL/start.sh \
    -argline $CATALINA_HOME || die "Script failed: start.sh"
# verify it is running. Wait up to 10s. Checks every 500ms
listening=`ctl -z -p demo -m netutil -c listening -- -port 28080 -maxwait 10`
[ "$listening" = "false" ] && die "Status failed: Tomcat not listening: (port=28080)"
echo "end start (4/4)"
 
echo "Deployment completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE"
 
# end v2/simpleTomcat.sh

stop.sh

The "stop.sh" script is conditionally called in Step#1 in simpleTomcat.sh. The "stop.sh" file is a shell script and wraps the Tomcat shutdown.sh script included in the Tomcat distribution.

File listing: stop.sh

#!/bin/bash
# File: stop.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
[ $# = 1 ] || { die "Usage: stop.sh <catalina-home>" ; }
[ -d "$1" ] && { 
    export CATALINA_HOME=$1
    export CATALINA_BASE=$CATALINA_HOME;
    $CATALINA_HOME/bin/shutdown.sh;
    exit $?
} || {
    echo "WARN: CATALINA_HOME not found: $1" ; 
}

configure.sh

Besides managing the runtime state of the deployed Tomcat instance, there is one more requirement for this example. We need to customize the Tomcat installation to not use its default set of ports.

There are actually several steps carried out in the configure.sh script:

  1. Set the execute bit on the Tomcat scripts in $CATALINA_HOME/bin.
  2. Create a backup of the $CATALINA_BASE/conf/server.xml
  3. Re-write the $CATALINA_BASE/conf/server.xml to replace the standard ports with ones that won't conflict with our environment.

The script makes use of the textutil's replace command to substitute default port settings with ones for the "simple" deployment.

File listing: configure.sh

#!/bin/sh
# File: configure.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
[ $# = 1 ] || { die "Usage: configure.sh <catalina-home>" ; }
[ -d "$1" ] || { die "CATALINA_HOME not found: $1" ; }
 
CATALINA_BASE=$1
echo "chmod'ing files in $CATALINA_BASE/bin/*.sh"
chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed"
 
echo "Customizing $CATALINA_BASE/conf/server.xml ..."
 
ctl -p demo -m textutil -c replace --\
 -basedir $CATALINA_BASE/conf -filebase server -token 8080 -value 28080 || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
ctl -p demo -m textutil -c replace --\
 -basedir $CATALINA_BASE/conf -filebase server -token 8005 -value 28105 || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
ctl -p demo -m textutil -c replace --\
 -basedir $CATALINA_BASE/conf -filebase server -token 8009 -value 28009 || {
    die "Text replace failed: $CATALINA_BASE/conf/server.xml"
}
 
echo "$CATALINA_BASE/conf/server.xml customized"

start.sh

The "start.sh" script is called in Step#5 in simpleTomcat.sh. The "start.sh" file is a shell script and wraps the Tomcat CATALINA_HOME/bin/startup.sh script included in the Tomcat distribution.

File listing: start.sh

#!/bin/bash
# File: start.sh
 
die() { echo "ERROR: $@" ; exit 1 ; }
[ $# = 1 ] || { die "Usage: start.sh <catalina-home>" ; }
[ -d "$1" ] || { die "CATALINA_HOME not found: $1" ; }
export CATALINA_HOME=$1
export CATALINA_BASE=$CATALINA_HOME;
echo "Invoking $CATALINA_HOME/bin/startup.sh"
$CATALINA_HOME/bin/startup.sh;
exit $?

v2/setup.sh

The setup.sh script is the same as in #setup.sh v1 except it will be modified to upload the new scripts to the webdav. Below is the snippet that is added to the setup.sh for this iteration.

#
# Upload the scripts
#
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts
ctl -z -p demo -m davutil -c put --\
   -file `pwd`/v2 \
    -url dav://examples/simpleTomcat/scripts/v2 || {
    die "Failed uploading scripts to DAV"
}

You can see that the put command can copy a directory of files to the WebDAV.

Running

v2/setup.sh

Like in Iteration One, the setup.sh script is invoked directly.

  1. cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v2
    • Change directory to the "v2" subdirectory
  2. sh ./setup.sh <node>
    • Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example: sh ./setup.sh centos2

v2/simpleTomcat.sh

Call the simpleTomcat.sh script via ctl-exec

Use ctl-exec to execute the simpleTomcat.sh script on each node that is tagged "simpleTomcat".

execute

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh

You should see output resembling the messages shown here:

output

Observations

The refactored implementation of the deployment process addressed several issues mentioned earlier. It is less monolithic, broken down into a two layers and leverages commands from coreutils.

The refactored design still suffers from one kind of inflexibility.

  • All or nothing execution. You can't invoke individual steps in the deployment cycle.

Iteration Three

Let's refactor the implementation again to address the shortcomings mentioned in the observations in Iteration Two. The basic refactoring strategy will be to:

  • Structure the code in the script to reflect the four main steps of the deployment cycle: stop, install, configure, start
    • This will be done by using shell functions for each of the 4 steps
  • Expose the individual steps as sub commands to the main driver script, "simpleTomcat.sh".
    • Option parsing will be added that accept named subcommands
  1. Define some jobs in Jobcenter
    • one job per function

The scripts

v3/simpleTomcat.sh

#!/bin/sh
# File: simpleTomcat.sh
PROG=`basename $0`
USAGE="
Usage: $PROG -c command
 -c,-command   Deploy command to run. Start|PackagesInstall|Configure|Stop|Deploy
"
die() { echo "ERROR: $@" ; exit 1 ; }
 
[ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; }
 
SCRIPTS_URL=dav://examples/scripted-appserver-war-deployment
TOMCAT_PKGBASE=apache-tomcat-5.5.28
SIMPLE_PKGBASE=simple
INSTALL_DIR=$CTIER_ROOT/pkgs
CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE
 
#
# Stop:
#
Stop() {
    listening=`ctl -z -p demo -m netutil -c listening -- -port 28080` ;
    [ "$listening" = "true" ] && {
	echo "Tomcat listening (port=28080). Running stop script ..." ;
	ctl -z -p demo -m shellutil -c exec --\
        -executable /bin/sh -scripturl $SCRIPTS_URL/stop.sh \
	    -argline $CATALINA_HOME || die "Script failed: stop.sh" ;
    }
    return 0;
}
 
#
# Packages-Install 
#
 
PackagesInstall() {
# Download the tomcat Zip
    ctl -z -p demo -m davutil -c copy --\
    -src dav://pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \
	-dest $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip" ;
    [ -d $CATALINA_HOME ] && {
	rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment" ;
    }
    cd $INSTALL_DIR ;
    unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip" ;
 
# Download the simple.war 
    ctl -z -p demo -m davutil -c copy --\
    -src dav://pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \
	-dest $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war" ;
# extract the WAR
    mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || {
	die "failed making $SIMPLE_PKGBASE directory" ;
    }
    cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE ;
    unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war" ;
}
 
#
# Configure
#
Configure() {
    ctl -z -p demo -m shellutil -c exec --\
    -executable /bin/sh -scripturl $SCRIPTS_URL/configure.sh \
	-argline $CATALINA_HOME || die "Script failed: configure.sh" ;
}
 
#
# Start
#
Start() {
    ctl -z -p demo -m shellutil -c exec --\
    -executable /bin/sh -scripturl $SCRIPTS_URL/start.sh \
	-argline $CATALINA_HOME || die "Script failed: start.sh" ;
# verify it is running. Wait up to 10s. Checks every 500ms
    listening=`ctl -z -p demo -m netutil -c listening -- -port 28080 -maxwait 10` ;
    [ "$listening" = "false" ] && die "Status failed: Tomcat not listening: (port=28080)" ;
    echo "Tomcat started. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE" ; 
}
 
#
# Status
#
Status() {
    listening=`ctl -z -p demo -m netutil -c listening -- -port 28080` ;
    [ "$listening" = "true" ] && {
	echo "Tomcat listening: (port=28080)"
    } || {
	die "Tomcat not listening: (port=28080)"
    }
}
 
#
# Deploy
#
Deploy() {
    Stop && PackagesInstall && Configure && Start;
    echo "Deployment completed.";
}
 
 
while [ "$#" -gt 0 ]; do
    OPT="$1"
    case "$OPT" in
    # options without arguments
	-h)
	    echo "$USAGE"
	    exit 0
	    ;;
	-c)
	    COMMAND="$2"
	    shift
	    ;;
    # end of options, just arguments left
	*)
	    echo "$USAGE"
	    exit 2 
	    ;;
    esac
    shift
done
 
case "$COMMAND" in
    Stop) Stop
	;;
    Packages-Install) PackagesInstall
	;;
    Configure)	Configure
	;;
    Start) Start
	;;
    Status) Status
	;;
    Deploy) Deploy
	;;
    *)
	break;
esac
 
# end simpleTomcat.sh

Usage

The usage of the new simpleTomcat.sh is shown below:

Usage: simpleTomcat.sh -c sub-command
 -c,-command   Command to run. Start|Packages-Install|Configure|Stop|Deploy

Because the simpleTomcat.sh now takes arguments, the ctl-exec command syntax changes slightly. For scripts that take arguments, you pass them after the "--" option:

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- [script-arguments]

For example, to run the "Status" sub-command in simpleTomcat.sh you add "-- -c Status":

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Status

When ctl-exec executes simpleTomcat.sh on the local node, it will be invoked as if you typed: simpleTomcat.sh -c Status

v3/setup.sh

The "setup.sh" script is the same as in Iteration Two. No changes were necessary.

Running

v3/setup.sh

Like in Iteration One and Two, the setup.sh script is invoked directly.

  1. cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v3/
    • Change directory to the "v3" subdirectory
  2. sh ./setup.sh <node>
    • Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example: sh ./setup.sh centos2

v3/simpleTomcat.sh

Deploy the tomcat script via ctl-exec

Use ctl-exec to execute the "Deploy" sub-command from the simpleTomcat.sh script across all nodes tagged "simpleTomcat".

execute: Deploy

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Deploy

output

number of nodes to dispatch to: 1, (threadcount=1)
Connecting to centos2:22
done.
Connecting to centos2:22
cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Connecting to centos2:22
cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Deploy
Deploy simple tomcat. Steps: stop, install, configure, start
begin stop (1/4) ...
Tomcat listening (port=28080). Running stop script ...
Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/stop.sh
To: /tmp/exec1458106545160442288-stop.sh
Copying 1 file to /tmp
Tomcat stopped
end stop (1/4)
begin install (2/4) ...
Getting: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/apache-tomcat-5.5.28.zip
To: /home/alexh/ctier/pkgs/apache-tomcat-5.5.28.zip
local file date : Sat Jun 20 15:08:03 PDT 2009
Not modified - so not downloaded
Getting: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war
To: /home/alexh/ctier/pkgs/simple.war
local file date : Sat Jun 20 15:08:04 PDT 2009
Not modified - so not downloaded
end install (2/4)
begin configure (3/4) ...
Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/configure.sh
To: /tmp/exec4481840577151954212-configure.sh
Copying 1 file to /tmp
Chmod'ing files in /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh
Customizing /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ...
/home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized
end configure (3/4)
begin start (4/4)...
Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/start.sh
To: /tmp/exec3072954032595161398-start.sh
Copying 1 file to /tmp
Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh
Tomcat started. Visit: http://centos2:28080/simple
end start (4/4)
Deployment completed. Visit: http://centos2:28080/simple
  • Run the "Status" subcommand:

execute: Status

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Status

output

number of nodes to dispatch to: 1, (threadcount=1)
Connecting to centos2:22
done.
Connecting to centos2:22
cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Connecting to centos2:22
cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Status
Tomcat listening: (port=28080)
  • Run the "Status" subcommand

execute: Stop

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Stop

output

number of nodes to dispatch to: 1, (threadcount=1)
Connecting to centos2:22
done.
Connecting to centos2:22
cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Connecting to centos2:22
cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Stop
Tomcat listening (port=28080). Running stop script ...
Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/stop.sh
To: /tmp/exec2039195157561216008-stop.sh
Copying 1 file to /tmp
Tomcat stopped
  • Run the "Start" subcommand

execute: Start

ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Start

output

number of nodes to dispatch to: 1, (threadcount=1)
Connecting to centos2:22
done.
Connecting to centos2:22
cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh
Connecting to centos2:22
cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Start
Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/start.sh
To: /tmp/exec1437938695406384114-start.sh
Copying 1 file to /tmp
Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh
Tomcat started. Visit: http://centos2:28080/simple


Personal tools
Development