Even Ants Can Be Assertive
Alan M. Berg
Scripts are solid building blocks that can be
glued together to create a strong edifice. Once in place, they will run with predictable regularity.
However, sometimes things go wrong; directory structures change, someone accidentally unpacks
a zip file in the wrong place as root, and Web applications fail to deploy properly. This article
follows my previous article "Platform Independent Stress Testing from the Command Line"
and emphasizes two themes. The first is how to handle unexpected errors gracefully. The second
theme is the use of Ant in an Apache Tomcat environment to centrally control deployment and assertion
testing. This second theme follows naturally in the lifecycle of a Web application from stress
testing a new infrastructure to more incremental deployments and assertions testing to ensure
the deployments are consistently healthy.
Introduction
In the previous article, I described the basics of using the Apache Ant tool
[1]. Ant reads build files that have a series of defined tasks. Tasks are succinct and powerful;
one task may be for archiving, another for scp or sending of mail. Series of tasks are accumulated
into targets. When running Ant, we need to tell Ant which target to run. Previously, I showed how
to stress-test systems via Ant and a third-party tool and then generate and publish the results.
In this article, I'll take the process further and show how, with the help of the Anteater project
[2], we can also perform assertion tests. On the way, I will also describe how to deploy Web applications
in Tomcat [3] via Ant and how to template configuration files, such as httpd.conf, to lower the risks
of entropy and the entropy-enhanced laws of Murphy.
Figure 1 defines a generic deployment process for Web applications.
The process begins in a development environment. After a significant development effort, the
beta application is transferred into an acceptance environment where functional tests are performed
including assertion tests. Assertion tests ensure that if specific actions take place, specific
results are returned. It is considered a good working practice to keep assertion testing away from
the developers as the results may be subjectively influenced by their wishful thinking.
Anteater is a feature-rich assertion testing system built on top of
Ant. Anteater uses the familiar build file structure. By capturing functional tests in this manner
via Anteater, the tests can be scheduled via cron to run at non-intrusive times without human intervention,
and the results can be read later. Further, the tests can be made configurable to run against different
machines with different expectancies. A lot of time, error, and effort is potentially removed
by this ongoing series of actions.
After being accepted, applications are placed into the production
infrastructure. Many of the configuration files in development tend to be similar in acceptance
and the production environments. For example, the Apache httpd.conf file may be the same in all
environments except for performance options and generic properties such as the server name. In
fact, it is bad practice not to have such a monoculture, as diversity brings with it extra maintenance
issues. By turning configuration files into templates and stamping the template for each environment,
we create a consistent set of configurations. This consistency ensures that debugging of issues
is easier and reduces the risk of typos. One can imagine a situation where configuration is done
on one central machine and the changes are pushed out to the various targets via one master Ant build
file. Better still, changes in a central place can be synchronized and stored in CVS.
This article is broken into four main Ant-related themes that support
the flow of new applications into production, as defined in Figure 1:
- Dealing with errors gracefully
- Templating httpd.conf files
- Deploying Web applications via Tomcat
- Assertion testing via Anteater
Dealing with Errors Gracefully
Entropy -- the movement from an ordered system to a chaotic one --
is a significant force in any systems administrator's daily life. Things go wrong; management
makes decisions! Hard to believe but it can happen. Hard drives fail, people get sick, directories
get removed, and permissions are changed. Certain types of mistakes are almost inevitable and
can be planned for. For example, expected settings may be missing in configuration files, directories
or files may not exist, scripts may get executed as the wrong user. Even the most hardened systems
administrator can forget to su.
Listing 1, example1.xml, employs tactics to deal with potential failure.
The listing highlights the generic tactic of first checking for core issues then, if necessary,
failing gracefully before doing any significant and everlasting damage that only the backups
can repair. Of course, you should not check for any potential error. Over-engineering adds new
forms of complexity and entertains the chance of injecting new self-generated errors. However,
I think with 10% effort, you can catch 70% of the problems. Of interest in example1.xml are the following
lines of code:
<tstamp><format property="today" pattern="yyyy_MM_dd_HH_mm"/></tstamp>
The property "today" is defined with a timestamp that generates
results similar to 2006_04_01_13_09. This pattern lists files in order when
using ls if the pattern is used as part of all the file names:
<available property="isDir" file="${dir.location}" type="dir" />
<available property="isFile" file="${file.location}" type="file" />
Checking for the availability of files or directories is achieved via the
<available> tag. The type of check is defined by the obviously named type attribute. In the
listing the properties "isDir" and "isFile" will only exist if the given file or directory exists.
Next, we need to accumulate the tests into one master control property
"isConfigured". This accumulation is achieved via the condition statement:
<condition property="isConfigured">
<and>
<isset property="isDir" />
<isset property="isFile" />
<isset property="property.value" />
<not>
<equals arg1="${user.name}" arg2="root" casesensitive="false"/>
</not>
</and>
</condition>
"isConfigured" will only be defined if the properties "isDir", "isFile",
and "property.value" exist. "property.value" is a configuration setting read in from example1.properties.
Note the use of the <not> tag. At this point you may be wondering where the variable user.name
derives from. Ant has a number of built-in properties. The user.name, as you would expect, is the
username of the owner of the process running. Check the target "info" for more examples
of these built-in variables. The <equals> tag tests the value in user.name against the case-insensitive
value "root". "isConfigured" is set when the file and directory exist, the "property.value" is
set, and the user is not root. Yes, basic stupidity testing for systems administrators working
late on an overloaded and busy Saturday evening.
What I have not shown is that the condition statement allows for other
tests as well, including which OS is running, the length of a string, whether a Web page is returned
without a 400 error from any given server, and much more [4].
The final piece of the puzzle is the answer to the question of how to fail
gracefully. One approach is to call a task that will cause failure if the "isConfigured" property
is not defined:
<target name="report_bad_configuration" unless="isConfigured" >
<echo>
System is not configured correctly
Directory exists ${dir.location} = ${isDir}
File exists ${file.location} = ${isFile}
property.value = ${property.value}
</echo>
<fail>FAILING GRACEFULLY....</fail>
</target>
The target will not run unless "isConfigured" exists. Notice that we use
echo for most of the failure message. This places the information above the BUILD FAILED stanza
(as shown next), which is more intuitive to read than below the stanza. Example1.xml should fail
generating a result similar to:
Buildfile: /usr/local/eclipse/workspace/sys_admin2/buildfiles/example1.xml
MAIN:
[echo] Trying to open logfile example1_2006_04_01_13_09_.log
report_bad_configuration:
[echo] System is not configured correctly
[echo] Directory exists example1 = true
[echo] File exists example1/does_not_exist.txt = ${isFile}
[echo] property.value = 30
BUILD FAILED
/usr/local/eclipse/workspace/sys_admin2/buildfiles/example1.xml:33: \
The following error occurred while executing this line:
/usr/local/eclipse/workspace/ sys_admin2/buildfiles/example1.xml:27: \
FAILING GRACEFULLY....
If you want all the conditions to be met, then you will need to uncomment the
correct value of file.location in the example1.properties file. Note that the build file logs
its actions via the record tag.
Templating Httpd.conf
When you have a development, acceptance, and production life cycle, then
you will have three slightly different sets of configuration. For example, the main Apache [5]
configuration file, httpd.conf, may have different server name and IP addresses to listen to,
perhaps tweaks in the performance setting, and occasionally extra modules need to be activated.
Instead of a scatological approach, you may consider using one template file that is stamped with
specific properties for each environment.
Advantages of this approach include a central repository where you
can consistently push out changes and view all the property value. With this extra stability, configuration
files are less prone to last-minute editing errors. Imagine making changes rapidly in a Monday
evening maintenance slot on your production systems. It is easier to change a property file of ten
lines and run Ant than to look through the 300 lines of target configuration, mostly comments to
zoom on a specific detail. You can do that, but opportunity knocks for extra downtime.
The core idea for templating is that Ant has a copy command that can search
the copied files for specified tokens. If the tokens arise, they can be replaced by variables defined
in property files. Listing 2 performs such a task. A template file is copied to a target directory
and stamped. The values that are set depend upon the property type in the example2._type.properties
file. By changing type, we can change which properties are set and hence have different values for
development, acceptance, or production. The key lines of code are:
<format property="now" pattern="MM/dd/yyyy hh:mm aa"/></tstamp>
The property "now" has a human-readable time stamp that later in the script
will by added as part of a comment to the top of the httpd.conf file:
<property file="properties/example2_name.properties"/>
<property file="properties/example2_${type}.properties"/>
The property type in example2_name.properties defines which
property file the rest of the variables are loaded from. So, for example, we may have type dev=development
or acc=acceptance.
HINT: If your structure is dissimilar qua operating systems (e.g.,
department A uses Apache on Windows and department B uses Unix), then you may also consider locally
templating the file and using a property file based on OS. This can be achieved via Ants built-in
variables, for example:
<property file="${os.name}.properties"/>
To add a token to the template file, surround the variable name with @@ (e.g.,
@EXTENDED.STATUS@). In the script itself, the following lines of code do the lifting work:
<filter token="EXTENDED.STATUS" value="${extended.status}"/>
<copy todir="example2/target" filtering="true" overwrite="true">
<fileset dir="example2/template" includes="httpd.conf" />
<globmapper from="*" to="*.${type}"/>
</copy>
The filter tag defines the token to be replaced. The copy tag does most of the
work. Fileset defines the files to be copied, and the globmapper copies file x with the extension
of the value defined in the variable type (e.g., ${type}=dev then httpd.conf is copied to httpd.conf.dev).
You may well be thinking that this method is useful for single properties,
but how about enabling or disabling whole features, such as an extra virtual host or functionality
like PHP? Well, this turns out to be reasonably simple. Suppose you have in httpd.conf the token
PHP.ENABLE, for example:
@PHP.ENABLE@
You may add the following filter to the script just before the copy tag:
<filter token="PHP.ENABLE" value="${php.enable}"/>
In the dev property file, you could include the line:
php.enable=Include /usr/local/apache/conf/php.w32.conf
And create the php.w32.conf file with content similar to:
LoadModule php5_module "d:/php/php5apache2.dll"
AddType application/x-httpd-php .php
PHPIniDir "d:/php"
In production, enabling PHP may not be valid, so you would add a value roughly
equivalent to:
php.enable=#PHP is not enabled in the production environment
In this section, we have seen that it is possible to template important and
complex configuration files via Ant allowing consistency and overview across infrastructure
boundaries. In the next section, we shall see how simple it is to deploy Web applications to Tomcat.
Web Application Deployment for Tomcat
Deployment of Web applications via war files under the control of Tomcat
is straightforward. A number of approaches are possible. You could, for example, copy the war file
directly to the webapps directory of the target Tomcat server and wait for the war file to be automatically
expanded and deployed. You could also place an expanded war file in the webapps directory, stop
the server, delete the work directory, and start the server. You could log in to the built-in management
application found under the URI /manager and upload the war file or even use an Ant task.
Each approach has pros and cons. For example, you may consider having
the management application installed as a potential security risk. Or, more likely, the administrator
wants to verify by hand every potentially dangerous action. Whichever approach you use, you should
verify that the action has taken hold. In this section I will mention the Ant task for deployment,
and in the next section I will discuss assertion testing via Anteater.
Listing 3 uses a Tomcat-specific Java library to define a new set of tasks.
The library that you will need for this task may be found relative to the home directory of any installed
Tomcat 5 server at server/lib/catalina-ant.jar. The taskdef verb loads in a property file with
a series of tasks pointing to the related Java class in the Catalina-ant jar file. The pathelement
attribute defines where the library may be found within the file system:
<taskdef file="properties/example3_tasks.properties">
<classpath><pathelement path="${lib.file}"/></classpath>
</taskdef>
In the property file, example3_tasks.properties, the deploy
tag (among others) is related to a specific Java class found in the Java library mentioned:
deploy=org.apache.catalina.ant.DeployTask
Example3.xml deploys the war file, stops the Tomcat servers, starts the
server, lists the running applications and the context they live under, removes the application,
then again lists contexts. A typical tag is the deployment tag; notice that the name of the application
is allowed to be different from the filename of the archive:
<deploy url="${manager.url}"
username="${user}"
password="${pass}"
path="/${war.name}"
war="file:${war.location}"/>
An important note on security -- always pass to the Ant build script the
username and password for the admin user of the Tomcat manager application via the -D command. This
is a better practice than carelessly leaving the information around on your file system. For example:
ant -Duser=admin -Dpass=secret -f build.filename
In this section I have shown you how straightforward it is to deploy and maintain
Web applications via Ant for Tomcat servers. Assertion testing will be described next.
Assertion Testing via Anteater
Assertion testing with Anteater is surprisingly simple. Using build files
with elementary tasks, you can quickly generate a whole range of test actions that can, via property
files, be fired off in the direction of any arbitrary piece of Web-enabled infrastructure. In this
section I will show you a basic test for a login page. This gives you a succinct hint of the potential
of Anteater.
Anteater, however, can do more. You can, for example, turn on Anteater's
own Tomcat server and let the tool listen for requests and test those requests against expected
values. A prime example of this features value is in Web service testing. In the world of Service-Oriented
Architecture, where one server may be a facade for accumulating other services, this may become
a priority feature.
Anteater is a self-contained package with the required Ant libraries
included. To install, download the package. Unzip and then add the underlying bin directory to
the PATH environment variable. Running anteater -f buildfile.name should work now. Anteater
relies on included Ant library files to work. However, if you want to run Anteater files from within
your standard Ant package, you will need to refer to the Anteater user manual [6].
Listing 4, example4.xml, runs three tests against the simple application.
You will find the application under the buildfiles/example3/simple.war directory in the source
code included with this article. Describing example4.xml: taskdef defines the Anteater task
and needs to be included in all build files:
<taskdef resource="META-INF/Anteater.tasks"/>
<typedef resource="META-INF/Anteater.types"/>
Anteater uses the concept of groups to configure different parts of
a test for different ranges of internal settings. Anteater has a standard set of control properties.
For example, haltonerror halts the build file if there is an error found by a given task. Some tasks
you may want to be able to halt the show, but others not. Therefore you would define one group with
haltonerror=true and another with haltonerror=false. For a full list of defaults that can be altered,
see references [7]:
<group id="local">
<property name="haltonerror" value="false" />
<logger type="xml" todir="${log.dir}"/>
</group>
The greatest work is done via the http tag. For example, the following snippet
posts to the URL defined by the variable url_parser. The method used is POST. The request sends
the parameters "user" and "pass" and expects a response of 200, but no content
with the string Error in it:
<httpRequest href="${url_parser}" group="local" >
<method>POST</method>
<parameter name="user" value="tester1" />
<parameter name="pass" value="badpassword" />
<match>
<responseCode value="200"/>
<regexp mustMatch="false" assign="n">Error</regexp>
</match>
</httpRequest>
The first point to note here is that error handling is specific to any given
application -- hence Anteater's need to error check for a string as well as the standard
return codes. The second point is that there is an implicit assumption in the test that the account
user tester1 exists. The code snippet has the test account hard-coded in. Best practices suggest
that the account should be placed in a property file, ready for alteration to match any testable
structure later.
Generating the report is handled by a built script. The anteater.report
location is built in and points to a build file that performs an XSL transformation on the XML result
file. This task results in a set of easily readable HTML pages:
<ant antfile="${anteater.report}">
<property name="log.dir" location="${log.dir}"/>
<property name="report.dir" location="${report.dir}"/>
</ant>
Conclusions
Ant is a powerful tool for doing heavy lifting within complex infrastructures.
By centralizing configuration control and deployment, you have a greater chance of consistency
and thus predictability of outcome. Error checking of common faults such as missing configuration
files or running under the wrong user helps in the fight against entropy.
Resources
1. Ant homepage -- http://ant.apache.org/
2. Anteater homepage -- http://aft.sourceforge.net/index.html
3. Tomcat homepage -- http://tomcat.apache.org/
4. Ant condition tests -- http://ant.apache.org/manual/CoreTasks/conditions.html
5. Apache homepage -- http://www.apache.org/
6. Anteater tasks from within Ant -- http://aft.sourceforge.net/manual/Invoking%20from%20Ant.html
7. Anteater group details -- http://aft.sourceforge.net/manual/Auxiliary%20tasks.html#elem:group
Alan M. Berg has been a lead developer at the Central Computer Services at
the University of Amsterdam for the past seven years. He likes to get his hands dirty with the building
and gluing of systems. In previous incarnations he was a technical writer, an Internet/Linux course
writer, and before that a science teacher. He remains agile by playing computer games with his kids
(Nelson and Lawrence) who sadly consistently beat him.
|