In one of my current projects I am working on increasing the quality of a large software development team. Besides a long list of measures this also includes establishing an automated build and integration test cycle. For web applications this also means an automated deployment into a container of choice. The existing project environment gave me the following constraints:
- Ant as build tool (I’d rather use Maven, but that’s how it is)
- Tomcat 5.x as container (this rules out e.g. Jetty, which might be from some points of view easier to handle for automated tasks)
- Should run on Windows and Linux (which kind of rules out running Tomcat executables)
- Should be able to perform different builds and Tomcat deployments in parallel (which rules out having one pre-installed Tomcat instance on the server which is used for deployments and tests)
Furthermore everything should be triggered from Hudson, which isn’t that difficult anymore once it runs in Ant. After doing some research and in a first attempt trying to implement start and stop routines myself in Ant I finally decided to drop that and give Cargo (http://cargo.codehaus.org) a shot. And frankly, I was exciting getting it to run. After all it was much less tedious than doing everything myself (well, that’s how it should be). I was a little disappointed about missing end-to-end examples which caused me to go the walk of trial and error until I have figured out what I wanted. That’s why I’d like to summarize what I did.
Cargo Ant Tasks
First of all download the two Cargo jar files and add the Ant tasks definition.
<taskdef resource="cargo.tasks">
<classpath>
<pathelement path="lib/cargo-ant-1.0.jar" />
<pathelement path="lib/cargo-core-uberjar-1.0.jar" />
<pathelement path="lib/commons-discovery-0.4.jar" />
<pathelement path="lib/commons-logging-1.1.1.jar" />
<pathelement path="lib/xercesImpl-2.4.0.jar" />
<pathelement path="lib/dom4j-1.6.1.jar" />
<pathelement path="lib/jaxen-1.1.2.jar" />
</classpath>
</taskdef>
Remarks:
- I didn’t get Tomcat 5.5.29 to work with the most recent version 1.0.1. After a little while I tried 1.0 and it worked fine for me. So for now I’ll just stick to that. I might look into this again later.
- Besides the two cargo JAR files I also had to include some dependencies. As I didn’t want to put them into the Ant classpath or give them as additional dependencies on command line I decided to include them here.
Starting up Tomcat via Cargo in Ant
The main piece of Ant code is about executing the Cargo task which looks in my case like this:
<cargo containerId="tomcat5x" home="tomcat-template" output="build/tomcat-output.log" log="build/cargo.log" action="start" wait="false">
<configuration type="standalone" home="build/tomcat-conf">
<property name="cargo.servlet.port" value="${tomcat.port}" />
<property name="cargo.logging" value="high" />
<property name="cargo.jvmargs" value="-Xms1024m -Xmx1024m" />
<deployable type="war" file="prepare/webapp1.war" />
<deployable type="war" file="prepare/webapp2.war" />
</configuration>
</cargo>
Remarks:
- I need to use Tomcat 5.5.29 so the Cargo containerId is set to tomcat5x. See also: List of available containers.
- The container “home” attribute is where Cargo is looking for a template Tomcat installation. In my case there is a full Tomcat as downloaded from the website but everything not necessary was removed (esp. all sorts of docs). The configuration attribute “home” is the directory where Cargo copies and configures the Tomcat instance that will be started up. In my case this is happening under a “build” directory. I also delete this directory at the beginning of every Ant build to have a 100% clean environment on every run.
- The action start tells Cargo to start up the container.
- The “wait” attribute is set to false, otherwise Cargo blocks Ant after the server is started. As I wanted to perform integration tests afterwards I needed a non-blocking startup. A nice debugging method is to set this attribute temporarily to true, run the Ant script, access your web app manually (e.g. with a browser) and then kill Tomcat again by cancelling your Ant execution with Ctrl+C.
- Since I am aiming at starting up multiple Tomcats at the same time I am assigning the “cargo.servlet.port” (which effectively sets the HTTP connector port in Tomcat’s servlet.xml) dynamically, i.e. with an Ant property.
- JVM args can also be passed to the container startup. For also: List of available properties.
- I have two wars that I want to deploy into this container so I also have two deployable tags. I put my wars into a prepare folder because I needed to manipulate them before running my tests. At this point Ant could pull the wars as well from build folders of other projects, from a repository or from whereever.
This launched my Tomcat successfully and without problems with Ant. Log output from Cargo as well as from Tomcat (the catalina.out) will be written into the specified logfiles.
Checking availability of your web app
At this point you can start to test your web application. You might want to add another little test before that in order to find out if everything went fine and your application is really actually available. One way of doing so works directly in Ant and looks like that:
<echo message="Ping web app..." />
<waitfor maxwait="10" maxwaitunit="second" timeoutproperty="webapp-not-available">
<and>
<socket server="localhost" port="${tomcat.port}" />
<http url="http://localhost:${tomcat.port}/myWebApp/testPath123" errorsBeginAt="201" />
</and>
</waitfor>
<fail if="webapp-not-available" message="Web app is not available." />
<echo message="Web app is available." />
Remarks:
- Note the Tomcat port, which is of course set to the same as above, as this will be set dynamically in my case.
- ErrorsBeginAt is set to 201, as this page should usually return a 200 OK and everything “above” should indicate an error to Ant.
That’s it
That was about everything it took to get Tomcat running under Windows and Linux and so far without problems. There aren’t any more problems also when the whole thing is started from Hudson, it just works well for me. Right now this configuration helps us to build up a pool of regression tests into our CI environment containing e.g. JMeter and Selenium tests.