J. Brisbin

Just another Wordpress.com weblog

Tomcat/tcServer Deployer using RabbitMQ

with one comment

I was prototyping a RabbitMQ-based web application deployer for SpringSource tcServer 6.0 today using the Groovy DSL for RabbitMQ. I’m starting to think this DSL is more than a simple administrative tool, though, because it took me about an hour (hour-and-a-half, tops) to create a consumer that listens to events emitted by the build server (in our case, TeamCity 5.0), downloads the new war file from the special URL and calls a couple very convenient JMX methods within tcServer to redeploy the application.

First Things First

First, I have to connect to the JMX server. The default port for tcServer’s JMX listener is 6969, and the login information is still set to the out-of-the-box default:

def p = "hostname -s".execute()
def hostname = p.text

def credentials = [
  "jmx.remote.credentials": ["admin", "springsource"].toArray(new String[2])
]

JMXServiceURL jmxUrl = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:6969/jmxrmi")
MBeanServerConnection mbeanServer = JMXConnectorFactory.connect(
  jmxUrl, credentials
).MBeanServerConnection

Now, simply set up an exchange and queue to listen for webapp cloud events:

mq.exchange(name: "vcloud.events.artifacts", type: "topic") {

  queue(name: "${hostname}.webapps", routingKey: "webapps.#") {
    consume tag: "${hostname}.webapps", onmessage: {msg ->
    }
  }

}

Since this is our machine’s personal queue, I want to listen for all webapp events, so I set a routing key of “webapps.#”, which just means “run me whenever any webapp update event happens”. If you wanted to get fancy, you could check the context path its asking you to update against your already-deployed context paths and ignore any requests that don’t pertain to you. But for the purposes of this quick demo, we’ll forgo the formalities there.

Download the File

The body of the event message is a URL link to the actual deployment artifact, which resides on the build server. We’ll simply open an InputStream and read that into a temp file we create locally:

println "Downloading new file..."
URL url = new URL(msg.bodyAsString)
InputStream bytesIn = url.openConnection().getInputStream()
File tempFile = File.createTempFile("webapps_", ".war", new File("/tmp"))
FileOutputStream tempFileOut = new FileOutputStream(tempFile)
byte[] buff = new byte[4096]
for (int bytesRead = bytesIn.read(buff); bytesRead > 0;) {
  tempFileOut.write(buff, 0, bytesRead)
  bytesRead = bytesIn.read(buff)
}
tempFileOut.flush()
tempFileOut.close()
bytesIn.close()

Thank you, SpringSource

Thanks to some special code that comes with tcServer, SpringSource has provided a JMX MBean to handle WAR file deployments. We’ll use Groovy’s special GroovyMBean class to make working with this bean even easier:

def deployer = new GroovyMBean(mbeanServer, "tcServer:type=Serviceability,name=Deployer")
deployer.undeployApplication("Catalina", "localhost", contextPath)
println "Deploying new war file..."
deployer.deployApplication("Catalina", "localhost", contextPath, tempFile.absolutePath)

Prototype or full-on Application?

This was meant to be a prototype. A “get this out of my head and into code” rough draft. Admittedly, it lacks pinache and, notably, error-handling. But this is still a pretty functional piece of Groovy code that can live on the tcServer nodes and listens to my RabbitMQ servers for update events from my build server.

Here’s the full code:

import javax.management.MBeanServerConnection
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

mq.on error: {err ->
  println "ERROR: ${err.message}"
}

mq {channel ->
  //channel.exchangeDelete("vcloud.events.artifacts")
}

def p = "hostname -s".execute()
def hostname = p.text

def credentials = [
  "jmx.remote.credentials": ["admin", "springsource"].toArray(new String[2])
]

JMXServiceURL jmxUrl = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:6969/jmxrmi")
MBeanServerConnection mbeanServer = JMXConnectorFactory.connect(
  jmxUrl, credentials).MBeanServerConnection

mq.exchange(name: "vcloud.events.artifacts", type: "topic") {

  queue(name: "${hostname}.webapps", routingKey: "webapps.#") {
    consume tag: "${hostname}.webapps", onmessage: {msg ->
      String type = msg.properties.headers["type"]
      println "Received ${type} event for URL ${msg.bodyAsString}"

      String contextPath = "/${msg.envelope.routingKey.substring(8)}"
      println "Context path: /${msg.envelope.routingKey.substring(8)}"

      // Download new file
      println "Downloading new file..."
      URL url = new URL(msg.bodyAsString)
      InputStream bytesIn = url.openConnection().getInputStream()
      File tempFile = File.createTempFile("webapps_", ".war", new File("/tmp"))
      FileOutputStream tempFileOut = new FileOutputStream(tempFile)
      byte[] buff = new byte[4096]
      for (int bytesRead = bytesIn.read(buff); bytesRead > 0;) {
        tempFileOut.write(buff, 0, bytesRead)
        bytesRead = bytesIn.read(buff)
      }
      tempFileOut.flush()
      tempFileOut.close()
      bytesIn.close()

      // Get MBean reference using special Groovy class
      def deployer = new GroovyMBean(mbeanServer, "tcServer:type=Serviceability,name=Deployer")
      // Deploy this application
      deployer.undeployApplication("Catalina", "localhost", contextPath)
      println "Deploying new war file..."
      deployer.deployApplication("Catalina", "localhost", contextPath, tempFile.absolutePath)

      println "Deleting temp file..."
      tempFile.delete()

      return false
    }
  }

}

send("vcloud.events.artifacts", "webapps.rest",
    [ "type": "updated", "source": "buildserver" ],
    "http://teamcityserver/path/to/rest.war")

Advertisements

Written by J. Brisbin

April 14, 2010 at 9:53 pm

Posted in RabbitMQ, The Virtual Cloud

Tagged with , ,

One Response

Subscribe to comments with RSS.

  1. […] This post was mentioned on Twitter by alexis richardson, J. Brisbin. J. Brisbin said: Just blogged about a #cloud deployer for #tcserver that uses #rabbitmq http://bit.ly/bRD7Te #springsource […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: