Hudson Continuous Integration LED message board monitor

Knowing the state of the build is one of the most important aspects of Continuous Integration. Team should get the information about the problems with he build as soon as possible. If the quality of source code degrades rendering the build to fail team, should jump in and fix the problem.
The most popular group of tools that make the swift reaction possible are the tray notifiers. Small programs that reside somewhere in the corner of the screen and show one of the 3 states the build server is ever in:
1. Building
2. Failed
3. Yet another successful build!
Oh, and well… not working (if the CI server is down). Some teams use emails or even SMS to inform about the broken build. Fore some it is not enough. The history of “alternative feedback mechanism” is long. It includes inventions like:
Ambient Orb
Read Bear Alert!
Traffic Lights (to tell you the truth it is my favourite!)
I didn’t wanted to be worse. I wanted my won gadget. My budget was limited so I’ve picked something cheap but funny. What can be cheaper than USB toy, I thought. But finding a device that can be customized/programmed was not so easy. Most of the USB toys are dummy devices that take the 5V from the USB socket to simply power something up. But after a small research I’ve stumbled upon something interesting: LED Message Board from Dream Cheeky. I’ve asked the manufacturer about the possibility to program the device and received short but complete instruction how to use it programmatically.
LED Message Board for Hudson CI server
The LED Message Board is a HID device. HID stays for Human Interface Device – a device that can interact with humans. In my case it was the device input that should interact with my developers and show them that something is wrong on the CI server. So I’ve looked for a HID library on the net and found a nice piece of work from Mike Obrien. A .NET Framework library ready to use with any HID device. Neat!
We are using Hudson CI at CODEFUSION (my company). It has a nice XML API (next to JSON and Python API) that anyone can use of. So I had everything I needed to start coding (for fun)!
Getting Hudson CI build state
As I mentioned earlier Hudson CI exposes quite robust XML API that you can consume over HTTP as you like. To get the basic information about all available APIs substitute your Hudson URL with API like this:
http://yourserver/hudson/api
To the the the basic XML API document write api/xml:
http://yourserver/hudson/api/xml
This document holds only the subset of information’s that Hudson CI can deliver. The fist level does not contain what I needed – the information if any of the jobs defined is in broken state. Trying another depths /api/xml?depth=1I found the last buildResult in jobs that was what I needed! You can filter the returned XML using the tree parameter like this:
http://yourserver/hudson/api/xml?tree=jobs[name,lastBuild[result]]
In return I’ve got nicely formatted XML with all the data I’ve needed
 

<hudson>
  <job>
    <name>S000.Framework</name>
    <lastBuild>
      <result>SUCCESS</result>
    </lastBuild>
  </job>
  <job>
    <name>S001.Cfms</name>
    <lastBuild>
      <result>SUCCESS</result>
    </lastBuild>
  </job>
</hudson>

 
All I needed now is to get the file and parse it. One more thing I had to do before it was possible. My Hudson CI server uses the Active Directory authentication. There is access without an AD account and a proper values in Hudson security matrix. After some investigation on the HTTP communication the browser is normally performing with the server (I used Firebug – excellent Firefox plug-in for web developers) I’ve came with following code:

 1: // Non blocking lock
 2: if (Monitor.TryEnter(_TimerLocker))
 3: {
 4:     // Create web client instance
 5:     MyWebClient WebClient = null;
 6:
 7:     try
 8:     {
 9:         WebClient = new MyWebClient();
 10:
 11:         // Log-in in if neccessary
 12:         if (_UserName != null && _Password != null)
 13:         {
 14:             // Readc login page and dump result to dummy string
 15:             string Dump = (new StreamReader(WebClient.OpenRead(_Uri.ToString() + "loginEntry"))).ReadToEnd();
 16:
 17:             System.Collections.Specialized.NameValueCollection Variables = new System.Collections.Specialized.NameValueCollection();
 18:             Variables.Add("j_username", _UserName);
 19:             Variables.Add("j_password", _Password);
 20:             Variables.Add("from", "/");
 21:             Variables.Add("Submit", "log in");
 22:
 23:             WebClient.UploadValues(_Uri.ToString() + "j_acegi_security_check", "POST", Variables);
 24:         }
 25:
 26:         StreamReader RequestReader = new StreamReader(WebClient.OpenRead(_Uri.ToString() + _HudsonFiler));
 27:         string ResponseFromServer = RequestReader.ReadToEnd();
 28:
 29:         XElement XElement = XElement.Parse(ResponseFromServer);
 30:
 31:         foreach (XElement Element in XElement.Elements())
 32:         {
 33:             // TODO mk from mk; meybe add project filtering
 34:             //Element.Element("name");
 35:             XElement LastBuildElement = Element.Element("lastBuild");
 36:
 37:             if (LastBuildElement != null)
 38:             {
 39:                 XElement IsSuccessResult = LastBuildElement.Element("result");
 40:
 41:                 if (IsSuccessResult != null && IsSuccessResult.Value.Equals("FAILURE"))
 42:                 {
 43:                     _IsOk = false;
 44:                     break;
 45:                 }
 46:             }
 47:
 48:             _IsOk = true;
 49:         }
 50:     }
 51:     catch (Exception ex)
 52:     {
 53:         _LogProvider.Error(ex);
 54:     }
 55:     finally
 56:     {
 57:         WebClient.Dispose();
 58:
 59:         // Exit from critical section
 60:         Monitor.Exit(_TimerLocker);
 61:     }
 62: }
 63: return _IsOk;

First, since I’m going to write a Windows Serve that periodically check Hudson CI I’m making a non blocking lock on the code I will run in a loop. If something hangs inside (like the HTTP request) the application will not run in a deadly loop that can eventually eat up all the resources. I’m using a overloaded WebClient class to perform all the operations. The overload was necessary because I have a secured Hudson CI server and the login is a little tricky in such case. Here is the code for the overloaded WebClient with cookie and changed timeout.

 1: namespace HudsonLedSygnalizer
 2: {
 3:     #region Using
 4:     using System;
 5:     using System.Net;
 6:     #endregion
 7:     internal class MyWebClient : WebClient
 8:     {
 9:         #region Private variables
 10:         private CookieContainer m_container = new CookieContainer();
 11:         #endregion
 12:
 13:         #region Overrides
 14:         protected override WebRequest GetWebRequest(Uri address)
 15:         {
 16:             WebRequest request = base.GetWebRequest(address);
 17:             if (request is HttpWebRequest)
 18:             {
 19:                 (request as HttpWebRequest).CookieContainer = m_container;
 20:                 (request as HttpWebRequest).Timeout = 6000;
 21:             }
 22:             return request;
 23:         }
 24:         #endregion
 25:     }
 26: }

What we are about to do with it is essentially log-in like we were to log-on to the site over normal web browser. Maintaining the session by providing session cookie to the server.
The login URL is:
http://yourserver/hudson/loginEntry
You will have to read this URL to get the response cookie that needs to be maintained. The actual return is of no use to use. We are interested only in cookies. Having the cookie we can log-in. To do so you will have to send POST request to the server containing:

  1. j_username – set to the Hudson account name
  2. j_password – set to the password for the Hudson account name
  3. from – set to “\”
  4. Submit set to “log in”

If you are using AD authentication the request needs to go to:
http://yourserver/hudson/j_acegi_security_check
I was using WebClient.UploadValues to get the POST request rolling.
From now on you should be authenticated and ready to get the information about last builds from Hudson CI. To do it you will have to use the API provided by the CI server that I described earlier in this post. I was only interested if any of the build is broken. So I searched in a job for a result that contained FAILURE text. If found I knew that the CI server as a whole is in broken state and consequently it was the time to lighten up the alarm signal on the LED Message Board.
Programming LED Message Board
Every HID device has a vendor and product ID. The two numbers are unique and you need them to contact the device. The ones for LED Message Board are

private int _VentodId = 0x1d34;
private int _ProductId = 0x0013;

Getting the device is easy with the Mike Obrien HID library I mentioned earlier:

 1: private void MyDetectMessageBoard()
 2: {
 3:     HidDevice[] HidDeviceList;
 4:
 5:     try
 6:     {
 7:
 8:         HidDeviceList = HidDevices.Enumerate(_VentodId, _ProductId);
 9:
 10:         if (HidDeviceList.Length > 0)
 11:         {
 12:             _MessageBoard = HidDeviceList[0];
 13:
 14:             _MessageBoard.Open();
 15:         }
 16:     }
 17:     catch (Exception ex)
 18:     {
 19:         _LogProvider.Error(ex);
 20:     }
 21: }

What I wanted to do is to simply display a “big red blinking eye” that watches my team if something is not working well on the CI server. To display the “big red eye” you will have to send following data to the device:

 1: byte[] Packet0 = new byte[] { 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x3F };
 2: byte[] Packet1 = new byte[] { 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x1F, 0xFF, 0xE0, 0x0F };
 3: byte[] Packet2 = new byte[] { 0x00, 0x00, 0x04, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x3F };
 4: byte[] Packet3 = new byte[] { 0x00, 0x00, 0x06, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF };

I wrote a quick Excel sheet to calculate the hex values needed for a given display pattern.
To display the signal you have to do the following:

 1: private void Timer_Elapsed(object sender, ElapsedEventArgs e)
 2: {
 3:     // Try to detect message board
 4:     if (_MessageBoard == null)
 5:     {
 6:         MyDetectMessageBoard();
 7:     }
 8:
 9:     // Try to send the signal
 10:     if (_MessageBoard != null)
 11:     {
 12:         _MessageBoard.Write(Packet0);
 13:         _MessageBoard.Write(Packet1);
 14:         _MessageBoard.Write(Packet2);
 15:         _MessageBoard.Write(Packet3);
 16:     }
 17: }

As you can see the I’m using the Timer to display the “big red eye”. It is because the diodes are fleshing for a fraction of a second and you have to keep up refreshing them to get continuous display. I used System.Timers.Timer class to do the work. I set the interval so that the LED will flash – for a better effect.

 1: internal LedNotifier()
 2: {
 3:     // Init timer
 4:     _Timer = new Timer();
 5:     _Timer.Enabled = false;
 6:     _Timer.Interval = 1000;
 7:     _Timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);
 8:
 9:     // Init message board
 10:     MyDetectMessageBoard();
 11:
 12: }

We almost have all the ingredients. The last thing is a windows service. It will be very simple service that incorporates a new timer to perform periodical checks on the Hudson CI server and if necessary turning on the LEDs. I’m not providing the source code here but you can download it here:
Fork me on git hub to get the sources!
That’s it hope you can find any use for the information provided in that post. Here last but not least the device in motion!

Hudson CI LED Message Board Notifier

2 Comments

  • Sanjoy

    Hi
    I would like to know the new job creation time or any existing job modification time through remote API. But I cannot find any exact help in the internet which could meet my requirement. The api documentation provided by Hudson also not enough to fulfill my need. Do you have any idea how can I fix this issue?
    Thanks in advance.
    Regards
    Sanjoy

    • marcin.kawalerowicz

      I’m not sure its possible. I’m afraid you will have to extend the API yourself 🙁

Leave a Reply

Your email address will not be published. Required fields are marked *