Sunday, June 13, 2010

TeamCity (continuous integration) integration with lava lamps

I wanted to hook up some lavalamps to our CI server.

Shopping list:
2 lava lamps (one red and one green, Teknikmagasinet in Sweden, 199:- sek a piece)
1 usb controlled power outlet (Silver Shield Power Manager bought at Kjell och Company in Sweden, 449:- sek)

The usb controlled poweroutlet comes with a command line program. I wrote this groovy script to poll the rss feed for our projects on the TeamCity server. The script has an added power save feature that allows you to configure it to turn off the lamps at weekends, it also allows you to only run the lamps between certain times of the day, say 08:00-19:00.

Have fun!


File: CILampControl.groovy
----------------------------
/**
 * User: Jonas Ekstrand
 * Date: 2010-maj-27
 * Time: 14:43:22
 */
def config = new ConfigSlurper().parse(new File('lampcontrol.config').toURL())

def LampController lampControl = new LampController(config.executable,
        config.deviceName, config.greenSocketName, config.redSocketName)
def TeamCityBuildFeed teamCityFeed = new TeamCityBuildFeed(config.feedUrl);

enum STATE {
  BUILD_SUCCESSFUL, BUILD_FAILED, LIGHTS_OFF
}
STATE lastState
//noinspection GroovyInfiniteLoopStatement
while (true) {
  // Get state, first check if lights should be on or off
  STATE state = getState(teamCityFeed, config.operateHours, config.operateWeekends)
  // Operate lights
  //noinspection GroovyVariableNotAssigned
  if (state != lastState)
    updateLamps(state, lampControl)
  // Update last state
  lastState = state
  // Print state
  println """${Calendar.getInstance().getTime()} $state
    Operate weekends:$config.operateWeekends Operate hours:$config.operateHours"""
  // Sleep
  Thread.sleep config.secondsBetweenChecks * 1000
}

private def updateLamps(STATE state, LampController lampControl) {
  switch (state) {
    case STATE.LIGHTS_OFF: lampControl.lightsOff(); return;
    case STATE.BUILD_SUCCESSFUL: lampControl.greenOnRedOff(); return;
    case STATE.BUILD_FAILED: lampControl.redOnGreenOff(); return;
  }
}

private STATE getState(TeamCityBuildFeed teamCityCI1, String operateHours, boolean operateWeekends) {
  //Should not operate during weekends and it is weekend OR is out of operation hours
  if ((!operateWeekends && !isWeekday()) || !isOperationHours(operateHours))
    return STATE.LIGHTS_OFF
  //Is build ok or not
  return teamCityCI1.getBuildStatus() ? STATE.BUILD_SUCCESSFUL : STATE.BUILD_FAILED
}

boolean isWeekday() {
  int day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
  return (day != Calendar.SUNDAY && day != Calendar.SATURDAY)
}

private boolean isOperationHours(String operateHours) {
  int hourNow = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
  def startAndStop = operateHours.split("-")
  int fromHour = startAndStop[0].toInteger()
  int toHour = startAndStop[1].toInteger()
  return hourNow >= fromHour && hourNow < toHour
}

class LampController {
  private String greenOnCmd, greenOffCmd, redOnCmd, redOffCmd

  public LampController(String executable, String deviceName,
                        String greenSocketName, String redSocketName) {
    greenOnCmd = "$executable -on -$deviceName -$greenSocketName"
    greenOffCmd = "$executable  -off -$deviceName -$greenSocketName"
    redOnCmd = "$executable  -on -$deviceName -$redSocketName"
    redOffCmd = "$executable  -off -$deviceName -$redSocketName"
  }

  def redOnGreenOff() {
    runCmd greenOffCmd
    runCmd redOnCmd
  }

  def greenOnRedOff() {
    runCmd greenOnCmd
    runCmd redOffCmd
  }

  def lightsOff() {
    runCmd redOffCmd
    runCmd greenOffCmd
  }

  private runCmd(String cmd) {
    Process process = cmd.execute()
    process.waitFor()
    process.destroy()
  }
}

class TeamCityBuildFeed {
  private String feedUrl;

  def TeamCityBuildFeed(feedUrl) {
    this.feedUrl = feedUrl;
  }

  def boolean getBuildStatus() {
    try {
      def feed = new XmlParser().parse(feedUrl)
      return feed.entry[0].'dc:creator'.text().equals("Successful Build")
    } catch (Exception e) {
      println "Could not get status from CI server $e"
      return false;
    }
  }
}
File: lampcontrol.config
----------------------------
/** How often (seconds) to check the for build state **/ secondsBetweenChecks=10 /** The url to the build server's rss feed for this project**/ feedUrl="http://SERVER_NAME/guestAuth/feed.html?projectId=PROJECT_ID&itemsType=builds&buildStatus=successful&buildStatus=failed&userKey=guest" /** If lights should be on during weekends **/ operateWeekends=false /** Between what hours should the lights be on. This settings assumes 24H clock. Use 0-24 for use round the clock **/ operateHours="8-19" /** Silver Shield Power Manager **/ executable="C:\\Program\\Gembird\\Power Manager\\pm.exe" deviceName="lava" greenSocketName="green" redSocketName="red"