Jetty Example

The Jetty example demonstrates a real world use of BlackBadger bringing together Jetty - the open source Java based web server - and Apache JMeter - an open source testing tool.

Jetty Component XML

The Jetty component is responsible for downloading and installing Jetty to it's host and running it on a given port. There are a number of parameters that provide some flexibility but it will work 'out of the box' with the provided defaults. The comments should provide a reasonable explanation of which each does.

  <!-- the port which Jetty will serve HTTP requests on -->
  <property name="port" value="8080"/>
  
  <!-- parameters used for stopping Jetty -->
  <property name="stop.port" value="8078"/>
  <property name="stop.key" value="secret"/>
  
  <!-- the name of the Jetty zip file -->
  <property name="filename" value="jetty-6.0.0.zip"/>
  <!-- the name of the Jetty file without the zip extension -->
  <basename property="name" file="${filename}" suffix=".zip"/>
  
  <!-- the url from which to download jetty - defaults to one of the public mirrors -->
  <property name="repository" value="http://heanet.dl.sourceforge.net/sourceforge/jetty/"/>  
  
  <!-- the location when Jetty will be installed -->
  <property name="installdir" value="/opt/"/>
  <!-- the top level directory where Jetty will be installed -->
  <property name="homedir" value="${installdir}${name}/"/>
  <!-- the directory to use for temporary file -->
  <property name="tmpdir" value="./tmp/"/>

The 'clean' target removes the home directory thus removing any previous installation and the files downloaded the temporary directory.

  <target name="clean" description="Called at the start to ensure the environment is clean for deployment">
    <delete dir="${tmpdir}"/>
    <delete file="${tmpdir}${filename}"/>      
  </target>

Deployment of Jetty is performed by the 'deploy' target. It downloads the Jetty zip file distribution from the url provided by the 'filename' and 'repository' parameters, and subsequently unzips it to the desired directory.

  <target name="deploy" description="Downloads Jetty and extracts the zip file">
    <mkdir dir="${tmpdir}"/>

    <echo message="Downloading Jetty..."/>
    <get src="${repository}${filename}" dest="${tmpdir}${filename}"/>
    <echo message="Download complete"/>
    
    <echo message="Extracting zip file..."/>
    <unzip src="${tmpdir}${filename}" dest="${installdir}" />
    <echo message="Complete"/>  
  </target>

Since Jetty provides a means of starting it via the 'start.jar' executable jar, we use the <java> task in the 'run' target to kick it off. It conveniently also provides a means of passing in the parameters we've set - the port, stop.port and stop.key. But because the process doesn't return whilst Jetty is stilling running we need to do 2 things. Firstly the process must be spawned (using the spawn attribute) so that the target can be executed to completion. Unfortunately by doing that we lose some of the other attribute functionality that would normally be useful in verifying that the process started up without error. The alternative we've used here is to wait for Jetty to return an http request on a url we know it should successfully return if the startup succeeded. We allow it up to a minute - checking every second - for it to respond. If it fails to respond in that period, the 'failedcheck' property is set and the <fail> task will subsequently cause this target to fail so that BlackBadger can be made aware of it. Otherwise, the startup is considered a success and we're ready to move onto the next state.

  <target name="run" description="Starts Jetty">
    <echo message="Starting up Jetty on port ${port}..."/>

    <java jar="${homedir}/start.jar" fork="true" maxmemory="128m" dir="${homedir}" spawn="true">
      <sysproperty key="jetty.port" value="${port}"/>
      <sysproperty key="STOP.PORT" value="${stop.port}"/>
      <sysproperty key="STOP.KEY" value="${stop.key}"/>
      <arg value="etc/jetty.xml"/>
    </java>
    
    <!-- wait until we can successfully hit a page on the server thus confirming it started up -->
    <waitfor maxwait="1" maxwaitunit="minute" checkevery="1000" timeoutproperty="failedcheck">
      <http url="http://localhost:${port}/test/index.html"/>
    </waitfor>
    
    <!-- if it didn't return successfully in the set timeout then abort -->
    <fail if="failedcheck" message="Failed to startup Jetty"/>
    
    <echo message="Jetty started up successfully"/>
  </target>

The test state in this instance is empty because it's at this point the JMeter component will be running it's tests. Everything that needs to be done to prepare Jetty to be tested against should have been done prior to this target.

  <target name="test" description="Called when the system is ready to start testing">
  </target>

Finally, the 'terminate' target performs the necessary java invocation to stop Jetty. Note that a similar <java> task is used but in this case we do not spawn the process because we want the shutdown to complete before we can consider the target execution at an end.

  <target name="terminate" description="Called when either an exception/error has occurred or the system is to stop.">
    <echo message="Shutting down Jetty..."/>
    <java jar="${homedir}/start.jar" fork="true" failonerror="true" maxmemory="128m" dir="${homedir}">
      <sysproperty key="STOP.PORT" value="${stop.port}"/>
      <sysproperty key="STOP.KEY" value="${stop.key}"/>
      <arg line="--stop"/>
    </java>
    <echo message="Shutdown complete"/>
  </target>

JMeter Component XML

The Apache JMeter component is designed for deploying JMeter on a machine and running a given test file. Again the properties defined at the top of the page are explained by the comments. Note that unlike the Jetty component however, there are 2 properties which although defaulted, require to be overridden in any run - the testrepository and testfile.

  <!-- the name of the JMeter distribution zip file --> 
  <property name="filename" value="jakarta-jmeter-2.2.zip"/>
  <basename property="name" file="${filename}" suffix=".zip"/>
  
  <!-- the url from which to download the jmeter installation -->
  <property name="repository" value="http://www.mirrorservice.org/sites/ftp.apache.org/jakarta/jmeter/binaries/"/>

  <!-- the url from which to download the test file -->
  <property name="testrepository" value=""/>
  <!-- the name of the test file to run -->
  <property name="testfile" value="test.jmx"/>
  <!-- the host that the testfile should be configured to use -->
  <property name="testfile.host" value="localhost"/>
  
  <!-- the temporary directory to use for downloaded files -->
  <property name="tmpdir" value="tmp/"/>

  <!-- the location when JMeter will be installed -->
  <property name="installdir" value="/opt/"/>
  <!-- the top level directory where JMeter will be installed -->
  <property name="homedir" value="${installdir}${name}/"/>
  
  <!-- the name of the jmeter jar to execute -->
  <property name="jmeter.jar" value="ApacheJMeter.jar"/>

As with the Jetty component, the 'clean' target removes any previous installation of JMeter and the files downloaded the temporary directory.

  <target name="clean" description="cleans up any existing files from a previous run">
    <delete dir="${jmeter.home}"/> <!-- delete any existing jmeter install -->
    <delete file="${tmpdir}${jmeter.tar}"/> 
    <delete file="${tmpdir}${testfile}"/>
  </target>

The 'deploy' target will download the JMeter distribution file and extract it to the installation directory. It also downloads the test file that will be used in the test run and edits the domain that it will run against - we're making the assumption that this is an http based testfile.

  <target name="deploy" description="installs JMeter and downloads the test file">
    <mkdir dir="${tmpdir}"/>
    
    <echo message="Downloading JMeter zip file"/>
    <get src="${repository}${filename}" dest="${tmpdir}${filename}"/>

    <echo message="Downloading JMeter test file"/>
    <get src="${testrepository}${testfile}" dest="${tmpdir}${testfile}"/>

    <!-- set the host that the testfile will run against -->
    <replace file="${tmpdir}${testfile}" value="&lt;stringProp name=&quot;HTTPSampler.domain&quot;&gt;${testfile.host}&lt;/stringProp&gt;">
      <replacetoken>&lt;stringProp name="HTTPSampler.domain"&gt;localhost&lt;/stringProp&gt;</replacetoken>
    </replace>

    <echo message="Extracting JMeter zip"/>
    <unzip src="${tmpdir}${filename}" dest="${installdir}"/>
    <echo message="Complete"/>  
  </target>

Nothing needs to be done in the 'run' target for JMeter.

  <target name="run" description="JMeter does it's running in the test target">
  </target>

Unlike the Jetty component, JMeter as the testing component does it's running in the 'test' target. Here we demonstrate an alternative to the <java> task in starting up the component - the <exec> task. The main argument we pass in here is the path to the test file that we want to run. Note that it's important that the 'failonerror' attribute is set to 'true' so that the target execution will fail as a whole if the JMeter startup fails and thus BlackBadger will be aware that the run failed.

  <target name="test" description="Runs the tests">
    <echo>Running JMeter...</echo>

    <exec executable="java" failonerror="true">
      <arg line="-jar ${homedir}bin/${jmeter.jar} -n -t ${basedir}/${tmpdir}${testfile}"/>
    </exec>
    
    <echo>JMeter run complete</echo>
  </target>

Last but not least, the 'terminate' target gathers the all important test results using the BlackBadger-specific <logfile> task. Note that it's possible that the test could have saved the results as any arbitrary filename but we only log the files with the ".log" file extension. However by recommending that all results are saved with this naming convention we can maximize the reusability of this component.

  <target name="terminate" description="Sends all the .log files back to the MasterBadger">
    <!-- 
      if the test file does any logging of results it should do so in the working
      directory with a filename extension of ".log" so that it's collected here
     -->
  
    <logfile>
      <fileset dir="${basedir}">
        <include name="*.log"/>
      </fileset>
    </logfile>
  </target>

System XML

The system xml is pretty straightforward. It consists of 1 agent with 2 components deployed on it - the component being tested and the testing component. In a real world testing scenario these would more likely be deployed on separate machines which is the reason behind the jetty 'ipaddress' property and the jmeter 'testfile.host' property that makes use of it.

We pass in a 'port' property to the Jetty component (overriding the default 8080) and the details of the test file that we wish to run to the JMeter component.

<?xml version="1.0" encoding="UTF-8"?>
<system name="Jetty example">

  <!-- 
    This example will download Jetty and JMeter, running a small 
    JMeter test file against the test application that's bundled
    with Jetty 
  -->

  <!-- the url where the JMeter test file can be downloaded from minus the filename -->
  <property name="testrepository" value="http://developer.spikesource.com/frs/download.php/121/"/>

  <agent id="agent1">
    
    <group id="server">
      <comp base="jetty" id="Jetty">
        <!-- startup jetty on port 80 -->
        <property name="port" value="80"/>
        <property name="ipaddress" value="${agent.this.ipaddress}"/>
      </comp>

      <comp base="jmeter" id="jmeter">
        <property name="testrepository" value="${system.testrepository}"/>
        <property name="testfile" value="jettytest.jmx"/>
        <property name="testfile.host" value="${component.jetty.ipaddress}"/>
      </comp> 
    </group>
  </agent>

</system>

Expected output

Below is how the output from a successful run might look:
05/10/06 16:32.35: MasterBadger-WeeBadger=WB_10.6002 registered; OS=Windows XP
05/10/06 16:32.45: StateMachine-WeeBadgers now setup with data entering execution states
05/10/06 16:32.45: StateMachine#clean-Executing State
05/10/06 16:32.45: RemoteWeeBadger:WB_10.6002-clean started
05/10/06 16:32.45: RemoteWeeBadger:WB_10.6002-Component.Jetty executing state: clean
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Deleting directory E:\SpikeSource\BlackBadger\tmp
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty completed state: clean
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.jmeter executing state: clean
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.jmeter completed state: clean
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-clean completed time=563ms
05/10/06 16:32.46: StateMachine#clean-Executing State Completed
05/10/06 16:32.46: StateMachine#deploy-Executing State
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-deploy started
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty executing state: deploy
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Created dir: E:\SpikeSource\BlackBadger\tmp
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Downloading Jetty...
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Getting: http://heanet.dl.sourceforge.net/sourceforge/jetty/jetty-6.0.0.zip
05/10/06 16:32.46: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] To: E:\SpikeSource\BlackBadger\tmp\jetty-6.0.0.zip
05/10/06 16:34.16: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Download complete
05/10/06 16:34.16: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Extracting zip file...
05/10/06 16:34.16: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Expanding: E:\SpikeSource\BlackBadger\tmp\jetty-6.0.0.zip into \opt
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Complete
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.Jetty completed state: deploy
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.jmeter executing state: deploy
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Downloading JMeter zip file
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Getting: http://www.mirrorservice.org/sites/ftp.apache.org/jakarta/jmeter/binaries/jakarta-jmeter-2.2.zip
05/10/06 16:34.29: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] To: E:\SpikeSource\BlackBadger\tmp\jakarta-jmeter-2.2.zip
05/10/06 16:35.35: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Downloading JMeter test file
05/10/06 16:35.35: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Getting: http://developer.spikesource.com/frs/download.php/121/jettytest.jmx
05/10/06 16:35.35: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] To: E:\SpikeSource\BlackBadger\tmp\jettytest.jmx
05/10/06 16:35.36: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Extracting JMeter zip
05/10/06 16:35.36: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Expanding: E:\SpikeSource\BlackBadger\tmp\jakarta-jmeter-2.2.zip into \opt
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Complete
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-Component.jmeter completed state: deploy
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-deploy completed time=170891ms
05/10/06 16:35.37: StateMachine#deploy-Executing State Completed
05/10/06 16:35.37: StateMachine#run-Executing State
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-run started
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-Component.Jetty executing state: run
05/10/06 16:35.37: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Starting up Jetty on port 80...
05/10/06 16:35.40: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Jetty started up successfully
05/10/06 16:35.40: RemoteWeeBadger:WB_10.6002-Component.Jetty completed state: run
05/10/06 16:35.40: RemoteWeeBadger:WB_10.6002-Component.jmeter executing state: run
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-Component.jmeter completed state: run
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-run completed time=3672ms
05/10/06 16:35.41: StateMachine#run-Executing State Completed
05/10/06 16:35.41: StateMachine#test-Executing State
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-test started
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-Component.Jetty executing state: test
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-Component.Jetty completed state: test
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-Component.jmeter executing state: test
05/10/06 16:35.41: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Running JMeter...
05/10/06 16:35.42: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Created the tree successfully
05/10/06 16:35.42: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Starting the test
05/10/06 16:37.27: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] Tidying up ...
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] ... end of run
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] JMeter run complete
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.jmeter completed state: test
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-test completed time=111343ms
05/10/06 16:37.32: StateMachine#test-Executing State Completed
05/10/06 16:37.32: StateMachine#terminate-Executing State
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-terminate started
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.Jetty executing state: terminate
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Shutting down Jetty...
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.Jetty.[ANT] Shutdown complete
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.Jetty completed state: terminate
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.jmeter executing state: terminate
05/10/06 16:37.32: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] sending files
05/10/06 16:37.33: RemoteWeeBadger:WB_10.6002-Component.jmeter.[ANT] 3 files sent
05/10/06 16:37.33: RemoteWeeBadger:WB_10.6002-Component.jmeter completed state: terminate
05/10/06 16:37.33: RemoteWeeBadger:WB_10.6002-terminate completed time=1296ms
05/10/06 16:37.33: StateMachine#terminate-Executing State Completed
05/10/06 16:37.33: StateMachine#terminate-All WeeBadgers now completed their state executions