The Current State
One of the limitations of CruiseControl.NET (CC.NET) is it’s handling of distributed builds. CC.NET does allow people to remote trigger a build, but that’s about it. Additionally we are only allowed to use .NET Remoting, so if the remote CC.NET server is behind a firewall, there is no distributed functionality.
“That’s ok, at least we can trigger builds” you say. The problem is, that’s it – no results, no monitoring whether the build was successful or not – just a trigger! Ideally, it would be nice to trigger a build, wait for it to finish and then import the results into the triggering project.
Now, there are some very valid reasons why triggering is the limit of what is currently offered. Valid, but not very nice! First, there is a queued nature of CC.NET – when a build is triggered it is added to a queue. There may be a long time period between the trigger and the project actually starting to build. Next, there is the issue of builds taking a long time to run – a build could start and not finish for a long time. Finally there is the issue of importing the results – the remote build has its own build log, and just importing it into the log can cause some interesting results (although to be fair this is more of an issue with some of XSL-T). And what if there are additional data fields that were not included in the log?
So, basically, we have a number of issues with the fundamental way that CC.NET works that prevent integrating remote builds. Or do we?
A Remote Project Task
With the changes for CC.NET 2.0, some of these issues are minimised, others can be worked around in the configuration. The stream changes mean the output from builds are now in separate files. Additionally the build log format has been changed to be more of an index, rather than a complete data store. Plus we have the ability to import any data files (or at least any that are in the artefacts folder). So these take care of the results issues – I’ll take more on the other issues later. Finally, we have a new remote client API that simplifies accessing servers over different protocols. So we can add an improved remote build task.
I have added a new task to the codebase called remoteProject. This task allows a person to trigger a build on a remote machine, waits for the build to finish and then import the results. This uses the new build log format and the data transfer mechanism, so all the results can be imported!
As a basic start, here is an example of the task:
<tasks>
<remoteProject projectName="Test" serverAddress="tcp://localhost:21234/CruiseManager.rem" />
</tasks>
This tells CC.NET to trigger the Test project on the server at the address tcp://localhost:21234/CruiseManager.rem. Any valid server address can be used – including HTTP and custom protocols. Additionally, the task supports a target attribute, so it can be used to trigger projects that sit behind a web dashboard.
How Does It Work?
Ok, this is where we show the nasty side of the task. The task basically does three things:
- Sends off a force build request to the remote server
- Waits for the remote build to finish
- Imports the results from the remote server
Step 1 is pretty simple – it’s almost exactly the same as the existing force build publisher. The main difference is is uses the new Remote API and checks the result of the request. If the request fails, the task will also fail (although it won’t error out like the publisher).
Step 2 is the nasty part – it enters into a polling loop while it waits for the remote project to finish. Every five seconds it polls the status of the build and waits for it to finish. This effectively means the build thread is locked while waiting for the remote build to finish. So, if the project is in a queue, no other projects will build until the remote build is finished! Told you it is nasty!
Step 3 is pretty simple, it gets the log from the remote build, iterates through all the results and imports them. Finally, the rebased log index is imported into the build log. I say rebased as all the files that were import will have their filenames changed to the new location (i.e. on the local server).
Getting Around the Polling Limitation
At the moment, there is no way around this limitation! If we want results, we have to wait
But there are some hacks that can minimise this wait":
- First, put the remote projects in their own queues (where possible). This means when a build is triggered it will start almost immediately.
- Second try not to make the remote builds too time consuming. If necessary, split them into smaller builds and call multiple projects concurrently (see the next point).
- Third, try to call remote build concurrently using the parallel task. This means the current project is waiting for multiple remote builds to finish, instead of just one.
In the future I hope to add a new “Paused” project status. This means other builds in the queue can continue building and when the remote builds have finished, the paused build will resume. However like a lot of things, this involves breaking CC.NET in some major ways so it won’t be an easy task.
I’m also planning on adding a call-back mechanism. This means instead of polling, the project will be paused. When the remote project has finished, it tell the paused project to continue (no need to poll!)
So, in the short term there is no way around polling, other than minimising the delay. In the future this will be smoother.
Use At Your Own Risk
At the moment this task is still very much in its infancy. In reality, it is more of a proof-of-concept. Yes, we can run remote builds and import the results, no it is not production ready. So let me know what you think, plus send me your ideas for how it can be improved. Also, it would be good to know about any limitations you can think of with this task.
And in case you are wondering, this is only a baby step. I still have other plans for making CC.NET distributed, included distributed tasks, automatic upgrades, configuration propagation, etc. But they take time, especially to do them properly
So, stay tuned…
RSS - Posts