Enterprise Integration Zone is brought to you in partnership with:

Ryan Carter is a Solution Architect and Author specializing in integration and APIs. Ryan is passionate about open source and is an appointed Mule champion regularly contributing to the community. Ryan is a DZone MVB and is not an employee of DZone and has posted 7 posts at DZone. You can read more from them at their website. View Full User Profile

Natively Running Mule on OpenShift

03.06.2013
| 3434 views |
  • submit to reddit
In this article I'll show how you can run Mule natively on OpenShift wihout using a Servlet container and show you how I got over a few implementation hurdles.

If you are familiar with Mule you know it gives you many deployment options including both standalone deployment or embedding itself within a Java application or Webapp. The recommended approach is to run Mule ESB standalone from the command prompt, as a service or daemon, or from a script. This is the simplest architecture, so it reduces the number of points where errors can occur. It's typically best for performance as well, since it reduces the number of layers and eliminates the inherent performance impact of an application server on the overall solution. With Mule 3.x, you can also now run multiple applications side by side in a Mule instance using the new deployment model can support live deployment and hot redeployment of applications. Lastly, standalone mode has full support for the Mule High Availability module and the Mule management console.

OpenShift gives you many choices for developing and deploying applications on the cloud. You can pick among PHP, Ruby, Perl, Python, Node.js or Java. As Mule is Java based, we are pretty much covered. OpenShift provides an end to end Java application stack including: Java EE6, CDI/WeldSpring and Spring. You can choose between multiple application server's for Webapps including JBoss AS7, JBoss EAP6, Tomcat, and GlassFish. But if you want to run Mule natively in standalone mode for the aforementioned benefits, you will need to create a "DIY" cartridge/application.

A "DIY" application is just a barebones app, with no server preloaded, ready to be tailored to your needs. With this app type, OpenShift is begining to blur the line between an IaaS and a PaaS, providing you with a controlled and scalable environment, and at the same time giving you the freedom to implement the technology that best suits your needs.

Getting Started

Before attempting to create a DIY application on OpenShift, you should familiarize yourself with the technology you are about to use. You should have a clear understanding of the steps needed to set it all up on your workstation and then reproduce it on OpenShift.

For a Mule application we won't need JBoss, or any application sever,  not even a servlet container at all. We just have to install Mule and start it up.

Doing this on your own workstation is as easy as downloading Mule, unzipping it, and then running:

./mule start

And to stop Mule we issue:

./mule stop

Now we'll have to do the same in our server at OpenShift. First, let's create a new application named "mule":

rhc app create -a mule -t diy-0.1

Now let's see what we created:

rhc app show -a mule

Now let's see what we created. Running the following script:

rhc app show -a mule

Should output similar to he following:

Application Info
================
mule
   Framework: diy-0.1
   Creation: 2013-01-19T07:14:34-06:01
   UUID: youruuid
   Git URL: ssh://youruuid@mule-yourdomain.rhcloud.com/~/git/mule.git/
   Public URL: http://mule-yourdomain.rhcloud.com/

You can browse to http://mule-yourdomain.rhcloud.com/ to see the default index page running. It's just the same static page you can find at raw/index.html

Now let's see what we have in our repo:

cd mule
ls -a
.git # our local git repo
misc # empty dir, you can just deleted it, no one will miss it
.openshift/action_hooks # this is where our hook scripts are located
raw # it holds the static page
README # some info

It's a pretty barebone app, but there's a folder that's quite interesting for us - .openshift/action_hooks:

ls .openshift/actions_hooks
build  deploy  post_deploy  pre_build  start  stop

Installing Mule

.openshift/action_hooks/pre_build

The pre_build script is used for downloading the required Mule installation and unzipping it.

# Get Install
curl -o ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1.tar.gz http://dist.codehaus.org/mule/distributions/mule-standalone-3.3.1.tar.gz

# Unzip
tar -zxvf ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1.tar.gz -C ${OPENSHIFT_DATA_DIR}

.openshift/action_hooks/start

Then to start the Mule server:

cd ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/bin
./mule start

.openshift/action_hooks/stop

And to stop the Mule server:

cd ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/bin
 
./mule stop

Upgrading the Java Service Wrapper

When you run the mule command, it launches the mule.bat or mule.sh script in your MULE_HOME/bin directory. These scripts invoke the Java Service Wrapper. The Java Service Wrapper from Tanuki Software is s fancy, little tool which helps with managing your application and JVM it is running in. By default it uses sockets to communicate back and forth with the JVM. But OpenShif is very restrcitive on what IP's and ports you are allowed to listen on.

By default the current Mule 3.3.1 release uses version 3.5.7 of the Java Service Wrapper. If you try running the default Mule instalation on OpenShift, you will get the folloqing error:

"unable to bind listener to any port in the range 32000-32999. (Permission denied)"

The Java Service Wrapper is controlled by a wrapper.conf file that can be found in you MULE_HOME/conf directory and has a host of configuration of options, including setting the rang eof ports that the wrapper can listen on. Ports aside, OpenShift only allows applications to bind on a specific IP address via the environment variable OPENSHIFT_INTERNAL_IP. Unfortunately there is no configuration option to override this IP address. Game Over!

Extra Life! In a later version of the wrapper, here is a new configuration option: wrapper.backend.type=PIPE to allow you to avoid using sockets and use pipes instead to get around this problem.

To upgrade the wrapper we simply download the later wrapper libraries and replace them within the MULE_HOME/lib directory.

.openshift/action_hooks/pre_build
# Remove wrapper
rm ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot/libwrapper-*
rm ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot/wrapper-*
 
# Get later version of wrapper
curl -o ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9.tar.gz http://wrapper.tanukisoftware.com/download/3.5.9/wrapper-delta-pack-3.5.9.tar.gz
    
# Unzip
tar -zxvf ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9.tar.gz -C ${OPENSHIFT_DATA_DIR}

# Remove Zip
rm ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9.tar.gz

# Copy and replace new wrapper files
cp ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9/lib/libwrapper-* ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot

cp ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9/lib/wrapper.jar ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot/wrapper-3.5.9.jar

rm ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot/exec/*

cp ${OPENSHIFT_DATA_DIR}wrapper-delta-pack-3.5.9/bin/wrapper-* ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/boot/exec

To update the wrapper.conf file with the new configuration. We take a copy of the original wrapper.conf file, ammended to contain the wrapper.backend.type=PIPE option and includ it within our git repo so that we can replace the original when building the instalation.

.openshift/action_hooks/pre_build

# Replace wrapper.conf
rm ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/conf/wrapper.conf

cp ${OPENSHIFT_REPO_DIR}wrapper.conf ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/conf

Deploying a Mule application

Deploying the applicaion is as simple as copying a Mule application archive to the required directory:

.openshift/action_hooks/deploy

cp ${OPENSHIFT_REPO_DIR}helloworld.zip ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/apps/

Where helloworld.zip is a simple Mule applicaion exposed over HTTP that returns "Hello World".

<flow name="HelloWorldFlow">
   <http:inbound-endpoint  exchange-pattern="request-response" host="${OPENSHIFT_INTERNAL_IP}" port="8080" />
    
    <set-payload value="Hello World!" />
</flow>

The only thing to take note of here is that we are using the ${OPENSHIFT_INTERNAL_IP} environment variable. This is the suggested IP address for creating socket connections on localhost. Typical values like "localhost", "127.0.0.1" and "0.0.0.0" are all locked down.

However, if you try using this environment variable as your host you will get an error similar to the following:

Permission denied (java.net.BindException) java.net.PlainSocketImpl:-2 (null) 2. Failed to bind to uri "http://127.8.109.1:8080"

As you can see; the internal IP resolves fine and we are using 8080 which is the suggested port for HTTP connections, but still no dice.

Hacking the TCP transport

After digging through the source, there is a slight issue with Mules' TCP transport.

public ServerSocket createServerSocket(URI uri, int backlog, Boolean reuse) throws IOException {
    String host = StringUtils.defaultIfEmpty(uri.getHost(), "localhost");
    InetAddress inetAddress = InetAddress.getByName(host);
    if (inetAddress.equals(InetAddress.getLocalHost())
            || inetAddress.isLoopbackAddress()
            || host.trim().equals("localhost")){
        return createServerSocket(uri.getPort(), backlog, reuse);
    }
    else {
        return createServerSocket(inetAddress, uri.getPort(), backlog, reuse);
    }
}

public ServerSocket createServerSocket(InetAddress address, int port, int backlog, Boolean reuse) throws IOException {
    return configure(new ServerSocket(), reuse, new InetSocketAddress(address, port), backlog);
}

public ServerSocket  createServerSocket(int port, int backlog, Boolean reuse) throws IOException {
    return configure(new ServerSocket(), reuse, new InetSocketAddress(port), backlog);
}

Here, the internal IP is a loopback address, so Mule forces it down the path of creating a Socket that listens on all interfaces for that port. Fortunately there is already af fix in the upcoming 3.4 release - MULE-6584: HTTP/ TCP bound to 127.0.0.1 listens on all interfaces.

Unfortunately, it's only upcoming at the moment. So instead I have ammended the source of this transport myself for the same functionality and included the resulting jar as part of my diy project to replace the orignal tranport jar.

.openshift/action_hooks/pre_build

cp ${OPENSHIFT_REPO_DIR}mule-transport-tcp-3.3.1.jar ${OPENSHIFT_DATA_DIR}mule-standalone-3.3.1/lib/mule

And that's it!

If you now take a look at your app at: http://mule-yourdomain.rhcloud.com/ you should now see "Hello World"! The full diy project for this with instructions can be found on GitHub: https://github.com/ryandcarter/mule-diy-cartridge




Published at DZone with permission of Ryan Carter, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)