Geeks With Blogs
Load "BLOG", 8, 1 This is the best I could do on short notice.

UPDATE: 27 July 2008 - New Version of Article on the CodeProject!

A new version of this article is now available on the CodeProject - click here to view it.



CruiseControl.Net is a great build server and for a while now I've been finding more and more uses for it, than just for continuous integration.

Recently I was listening to Scott Hanselman on his podcast HanselMinutes, interview Owen Rogers regarding Continuous Monitoring.

I thought this would be a good time to show how easy it is to setup CruiseControl.Net and show the necessary steps to quickly install, configure and have CruiseControl.Net run an existing scheduled task you may have.

In order for CruiseControl.Net to be useful as a monitoring server you need to be able to:
  • Schedule tasks to occur at certain intervals, just like you would schedule a task in Windows using Cruise Control Triggers
  • Provide feedback to users on whether the task failed/passed, by merging output from the monitoring application into the build report page

This article will focus on combining the scheduling aspect with a simple program that monitors a Url.
Once you've seen the idea behind how to set everything up, its quite easy to extend this concept to meet your  individual needs such as:
  • Health monitor for key systems (database, webservice etc)
  • Report generation
  • Produce content to be consumed via RSS or exported via FTP for other applications
Steve Trefethen has created a customised solution using CruiseControl, as part of a EDI Invoicing project he did, check out the article for some great ideas on what can be done.


Getting Stared

You can download CruiseControl.Net 1.3 from here and I've installed it with all the default options.
Once you've installed CruiseControl.Net, it should have configured a virtual directory called ccnet under your default website and installed and started the CruiseControl.Net service.

Check that all is running ok by going to http://localhost/ccnet

You should see the web dashboard load but with no projects configured.

NAnt will be used by CruiseControl.Net to run our console application.
You can download NAnt 0.85 from here ( nant-0.85-bin.zip) and then extract it to where ever your utils go.

Now we are ready to:
1) Create the simple console application to monitor a Url
2) Configure and schedule CruiseControl.Net to run our application
3) Merge the console output into the build report for all to see


The Monitoring application

The monitoring application is a simple console application which will return 0 or 1, depending on whether a check on a Url passes or failes.

The application writes out to the console some simple information on the status of what its done.
Whatever the console application outputs will be store as part of the NAnt build information and merged into the build report.

If you program threw an exception that wasn't caught, CruiseControl will assume a fail.

The c# code for console application is as follows:

Program.cs
using System;
using System.Net;

namespace MonitoringApplication
{
class Program
{
static int Main(string[] args)
{
string url = args[0];
string timestamp = DateTime.Now.ToString("HH:mm:ss ");
try {
WebRequest request = WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Console.WriteLine(timestamp + " Successfully connected to: "+ url);
return 0;
}
catch (Exception ex)
{
Console.WriteLine(timestamp + " Failed to connect to: "+ url);
Console.WriteLine(timestamp + " "+ ex.Message);
return 1;
}
}
}
}

Setting up our Project in CruiseControl.Net


Scheduling a Project
For our health monitoring example we would like it to run a console application and be able to schedule it to run at certain times.

The following shows how to setup the <triggers> part of your  <project> definition in your ccnet.config, in order to achieve the schedule set out below:
  • the program will run Monday - Friday, between 8am and 6pm, every 15 minutes
  • doesn't run on Saturdays and Sundays
You will need to specify your own <workingDirectory > in the config below, this directory should contain the application and the build file.

C:\Program Files\CruiseControl.NET\server\ccnet.config
<cruisecontrol>    
<project name="Url Test">
<workingDirectory>[specify your working directory]</workingDirectory>
<triggers>
<filterTrigger startTime="18:00" endTime="08:30">
<trigger type="filterTrigger" startTime="0:00" endTime="23:59:59">
<trigger type="intervalTrigger" name="continuous" seconds="900" buildCondition="ForceBuild"/>
<weekDays>
<weekDay>Saturday</weekDay>
<weekDay>Sunday</weekDay>
</weekDays>
</trigger>
</filterTrigger>
</triggers>
<tasks>
<nant>
<executable>C:\Program Files\Utils\nant-0.85\bin\NAnt.exe</executable>
<nologo>true</nologo>
<buildFile>C:\dev\MonitoringApplication\bin\Debug\urltest.build</buildFile>
<targetList>
<target>UrlTest</target>
</targetList>
<buildTimeoutSeconds>300</buildTimeoutSeconds>
</nant>
</tasks>
</project>
</cruisecontrol>
For further information on the FilterTrigger see the documentation here and more detailed information on the NAnt <task> can be found in the documentation here.

Creating the build file to run the application
The <buildFile> that is referred to in the above file ccnet.config, is what CruiseControl.Net will use to execute the console application.

You can see that in the build file I am passing the Url I want to test via the commandline parameter of the <exec> task.

I could setup numerous targets if I wanted to test more Urls or modify the console application to read a list from a text file, database etc.

<whatever you have set the workingDirectory to>\urltest.build
<?xml version="1.0" ?>
<project name="Url.Test" default="UrlTest" xmlns="http://nant.sf.net/schemas/nant.xsd">
<property name="root" value="${path::get-full-path('.')}"/>
<property name="name" value="MonitoringApplication.exe"/>
<target name="UrlTest">
<exec verbose="true" append="true" output="urltest.log" program="${root}\${name}" commandline="http://www.yahoo.com" />
</target>
</project>
You should now be able to see your project on the web dashboard at http://localhost/ccnet, and force a build to test the console application.
Test using a valid and invalid Url and you'll see what gets reported, you'll notice though that on a failure, your not told what was wrong, just that it failed.

build failed with no console output as part of report

CruiseControl.Net won't run my program or says there's some error, where can I find the build or NAnt Log?
The first thing to do is to check that you build file works correctly from a command prompt.

Then, if you are still having problems check the CruiseControl.Net build and the NAnt log files.
These can be accessed by clicking on the build you would like to inspect and selecting the appropriate link on the left.

If your using relative paths to your <workingDirectory> that you specified in the <project> part of the ccnet.config, try using the full path to eliminate any path errors.

highlighting where build and nant log are


Merging the console output into the build report page

Now that you've created the sample application, added the scheduled project to CruiseControl and the NAnt script to run the application, its time to modify the build report page to see the results produced by the application.

By default the console application output is not included in the build report page.

This doesn't matter so much when everything is working, but when the application fails it would be nice to see what happened via the CruiseControl.Net web dashboard, without having to hunt through log files.

In order to see the console application output, we will modify the xsl of the build report page header to include what was written out to the console by the application.

Open up the header.xsl file for the webdashboard and add the changes that are in bold below:

C:\Program Files\CruiseControl.NET\webdashboard\xsl\header.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:variable name="modification.list" select="/cruisecontrol/modifications/modification"/>
<xsl:variable name="messages" select="/cruisecontrol//task[@name='exec']/message" />
<
xsl:variable name="messages.count" select="count($messages)" />

<
table class="section-table" cellpadding="2" cellspacing="0" border="0">
  <xsl:if test="/cruisecontrol/exception">
<tr><td class="header-title" colspan="2">BUILD EXCEPTION</td></tr>
<
tr>
<td class="header-label" valign="top"><nobr>Error Message:</nobr></td>
<td class="header-data-error"><xsl:value-of select="/cruisecontrol/exception"/></td>
</tr>
</xsl:if>
<xsl:if test="/cruisecontrol/build/@error">
<tr><td class="header-title" colspan="2">BUILD FAILED</td></tr>
</xsl:if>
<xsl:if test="not (/cruisecontrol/build/@error) and not (/cruisecontrol/exception)">
<tr><td class="header-title" colspan="2">BUILD SUCCESSFUL</td></tr>
</xsl:if>
<tr>
<td class="header-label"><nobr>Project:</nobr></td>
<td class="header-data"><xsl:value-of select="/cruisecontrol/@project"/></td>
</tr>
<tr>
<td class="header-label"><nobr>Date of build:</nobr></td>
<td class="header-data"><xsl:value-of select="/cruisecontrol/build/@date"/></td>
</tr>
<tr>
<td class="header-label"><nobr>Running time:</nobr></td>
<td class="header-data"><xsl:value-of select="/cruisecontrol/build/@buildtime"/></td>
</tr>
<tr>
<td class="header-label"><nobr>Integration Request:</nobr></td>
<td class="header-data">
<xsl:value-of select="/cruisecontrol/request" />
</td>
</tr>
<xsl:apply-templates select="$modification.list">
<xsl:sort select="date" order="descending" data-type="text" />
</xsl:apply-templates>
<tr>
<
td colspan="2" class="header-data"><br/></td>
</
tr>
<tr>
<td colspan="2" class="sectionheader">Console Output</td>
</tr>
<
tr>
<td colspan="2" class="header-data">
<xsl:choose>
<xsl:when test="$messages.count > 0">
<xsl:apply-templates select="$messages"/>
</xsl:when>
<xsl:otherwise>No console ouput found</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</table>
</xsl:template>

<
xsl:template match="message">
<xsl:value-of select="text()"/><br/>
</xsl:template>

<
xsl:template match="/cruisecontrol/modifications/modification">
<xsl:if test="position() = 1">
<tr>
<td class="header-label"><nobr>Last changed:</nobr></td>
<td class="header-data"><xsl:value-of select="date"/></td>
</tr>
<tr>
<td class="header-label" valign="top"><nobr>Last log entry:</nobr></td>
<td class="header-data"><pre><xsl:value-of select="comment"/></pre></td>
</tr>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
An explanation of how XSL works is beyond the scope of the article but I'll briefly discuss the highlight areas above.

First we setup our condition to extract the right elements from the build log file.
You could make this smarter if your console application outputted a keyword per line and you only pickup specific messages with the keyword, instead of anything written to the output.

Next we add the conditional element to display the console output.

Finally, we add a template that will control how we display each element we've extracted.

Now when the build fails you'll get all the information that the console application wrote out to the console.

build failed with console output as part of report

Since I'm not retrieving anything from source control I could remove the Modifications since last build section as this will always have nothing in it.

Whenever the monitoring application fails, the results are easily available for any users via the CC.Net Web dashboard and those subscribed to the Project using CCTray will be notified too.

CCTray can be downloaded from here and allows users to subscribe to specific projects that they are interested in and be notified on the ongoing status.

CCTray


Please contact me with any comments, suggestions and feedback!


Revision History
2008-06-09    - Article Published
Posted on Monday, June 2, 2008 10:01 PM | Back to top

Copyright © Ralph Willgoss | Powered by: GeeksWithBlogs.net