Distributed Builds: Part 4 (of 4+)
Quick Recap
This is the fourth in my series on adding distributed builds to CruiseControl.NET. Previously I’ve covered some of the issues, a basic design and some scenarios that need to be covered.
In this post I’ll start to look at how we can possibly configure a distributed build scenario.
Starting Off Easy
Of the six scenarios in my last post (here) the simplest one to implement is the single remote build scenario (well, the local build scenario is easier, but then nothing is distributed!) So I’ll look at how I can configure a project to be distributed.
NOTE: this is a work in progress, it is only my current ideas of how to implement it. Hopefully with feedback we will be able to improve it over time.
Machine Definitions
The configuration needs two sections. First we need to define some remote machines. These definitions provide the connection and metadata information about the remote machine. For example, we can define the following machines:
<remoteMachine name="buildAgent1">
<address>http://winMachine</address>
<configuration>
<namedValue name="OS" value="Windows7" />
<namedValue name="Browser" value="IE8" />
<namedValue name="Browser" value="Firefox" />
</configuration>
</remoteMachine>
<remoteMachine name="buildAgent2">
<address>http://linuxMachine</address>
<configuration>
<namedValue name="OS" value="Ubuntu" />
<namedValue name="Browser" value="Firefox" />
</configuration>
</remoteMachine>
buildAgent1 has a target address of http://winMachine (at some future point we need to do some more work around the addresses, but it will do for now.) When a project builds it will send the request to this address. It also has some configuration values – it defines an OS and two browsers. buildAgent2 is very similar, but it has a different address and configuration values.
The address element will be mandatory (after all it is very hard to send a request to the machine if its address is unknown!) while the configuration element will be optional. The configuration element is basically to provide hints to the project on which machines it will choose.
These elements will be top-level elements in the configuration (i.e. direct children of <cruisecontrol>.)
Project Changes
Once the machines have been configured, we then need to hook up the project to the machine. This can be configured like so:
<project name="distributedTest"> <buildTarget xsi:type="singleTarget" name="buildAgent1" /> <!– Other configuration goes here –> </project>
This defines the build target as a single remote machine with the name of “buildAgent1”. When the project builds it will look in the list of machines and find the machine with the name of “buildAgent1”. This machine will then receive the request to build.
This can also be expanded to allow configuring the local build:
<project name="distributedTest"> <buildTarget xsi:type="singleTarget" name="buildAgent1" runLocal="OnFailure" /> <!– Other configuration goes here –> </project>
At this point I am thinking of allowing three runLocal options:
- Never – never run a local build
- OnFailure – run the build locally if the remote build fails (e.g. the machine cannot be contacted or rejects the request)
- Always – always run a local build
The default would be OnFailure.
Handling Multiple Machines
Once we have the simple scenario, we can look at expanding this to handle multiple build targets. This would be done by adding a new build target type:
<project name="distributedTest">
<buildTarget xsi:type="multipleTarget" runLocal="OnFailure" number="All">
<targets>
<singleTarget name="buildAgent1" />
<singleTarget name="buildAgent2" />
</targets>
</buildTarget>
<!– Other configuration goes here –>
</project>
This expands the original singleTarget by allowing multiple targets to be specified. The targets are specified with the same singleTarget definition (allowing re-use.) The runLocal attribute will work in the same way as runLocal on a singleTarget (note, runLocal will be ignored on the child targets.)
The number attribute specifies how many target machines to run on. This can be either a number or the keyword “All”. When “All” is specified the build will be run on all the specified targets.
Using Configured Machines
The above configuration handles most of the base scenarios, the only one uncovered is using configuration. This will be a new target type:
<project name="distributedTest">
<buildTarget xsi:type="configurationTarget" runLocal="OnFailure">
<configuration>
<namedValue name="Browser" value="Firefox" />
</configuration>
</buildTarget>
<!– Other configuration goes here –>
</project>
When the project runs it will try to match a machine with the same configuration (note there are no limits on what configuration values can be specified, so it is up to the administrator to decide what values are available.) This can then be combined with the multipleTarget to handle different configuration combinations:
<project name="distributedTest">
<buildTarget xsi:type="multipleTarget" runLocal="OnFailure" number="1">
<targets>
<configurationTarget>
<configuration>
<namedValue name="Browser" value="Firefox" />
</configuration>
</configurationTarget>
<configurationTarget>
<configuration>
<namedValue name="OS" value="Windows*" />
</configuration>
</configurationTarget>
</targets>
</buildTarget>
<!– Other configuration goes here –>
</project>
Configuration Wrap-up
These are my basic plans for the configuration for distributed builds. These configuration elements cover all my basic scenarios, so in theory we can build a project in a number of different ways. Also this allows for people to add their own targets (round-robin, multiple-attempt, etc.)
However, there is still a side of the configuration that has not been covered – how to configure the remote machine? In my next post I will look into how I am thinking of doing that.
Stay tuned…