Deploying your C application as a grid service using the IBM Grid Toolbox for Multiplatform

Jean-Yves Girard (girardjy@fr.ibm.com)

IT Specialist – IBM EMEA Design Center for ebusiness on demand

Ioane Muni Toke (munitoke@mas.ecp.fr)

Phd student – Ecole Centrale Paris Applied Mathematics and Systems laboratory Research laboratory

Jean-Luc Lepesant (lepesant@fr.ibm.com)

Software Engineer - IBM EMEA Design Center for ebusiness on demand

Jean-Pierre Prost (jpprost@fr.ibm.com)

Lead IT Architect - IBM EMEA Design Center for ebusiness on demand

 

March 2004

Introduction

Grid services introduce a new programming model where applications are abstracted through their interface to enable cross-platform and cross-programming language interoperability.  The purpose of grid services is to break application silos by allowing the virtualization of the IT resources where the application is running.

Using grid services does not mean you have to rewrite all your applications.  The IBM Grid Toolbox Version 3 for Multiplatform (referred to from now on as the IBM Grid Toolbox or IGT) provides a set of tooling to easily create grid services from existing applications.

In this article, we illustrate how a grid service can be created from a C function and deployed into the IBM Grid Toolbox container.

Grid service enablement

Let us consider a simple C function:

char* getResult(int n,char* s) {
        unsigned int l = strlen(s);
        int i;
        char *res;
        res=(char*)malloc(n*l+1);
        *res='\0';
        for(i=0;i<n;i++) {
               res=strcat(res,s);
        };
        printf("\n%s\n",res);
        return res;
};

This function takes a string s and an integer n as parameters and returns the concatenation of n instances of s. 

As a grid service, this function will be invoked in a service instance hosted in what is called a hosting environment.  A hosting environment is the runtime environment in which a grid service is deployed.  The Globus Toolkit 3 container and the IBM Grid Toolbox container are such hosting environments.  In the grid service programming model, a Factory service is used to create grid service instances per user, per connection …

Then, the grid service enablement of the getResult() function will require the following two tasks:

-          Create the grid service that will call the getResult() function

-          Create the grid factory service that will create the instance of the previous service.

Fortunately, the IBM Grid Toolbox provides the necessary tooling to automatically perform these two tasks.

IBM Grid Toolbox presentation

The IBM Grid Toolbox is a commercial release of the Globus Toolkit Version 3 (GT3), based on a Websphere Application Server runtime environment. It is easily installable through an integrated wizard, which provides the same look and feel on all supported platforms and allows for the selective deployment of the grid services provided by IGT. In addition to the GT3 grid services (program execution services, a.k.a. GRAM3, information services, a.k.a. MDS3, and data management services, e.g. Reliable File Transfer), IGT provides a service group based registry service, policy management services, and common manageability model (CMM) services allowing to expose CIM (Common Information Model) instrumented resources as grid services. IGT also features a web-based application which allows for grid service management.

The IBM Grid Toolbox is currently available on xSeries Linux through web access direct download and on pSeries AIX through your local IBM representative (product number 5765-G22). Support for zSeries Linux, pSeries Linux, and iSeries Linux, is planned.

Creating a Java wrapper

The hosting environment provided by the IBM Grid Toolbox can host grid services developed with the Java programming language.  To host our getResult() implementation we must create a Java wrapper class that will invoke the native C implementation.   This Java wrapper will be exposed as a grid service using the IBM Grid Toolbox tooling.

The Java Native Interface (JNI) allows the call of native C written functions from a Java program.  It may be tricky to write a Java wrapper with JNI calls. However, SWIG (Simplified Wrapper and Interface Generator) is an open software development tool that connects programs written in C and C++ with a variety of high-level programming languages, and will do the work for us.

The SWIG tool

SWIG expects an input file, usually denoted with a .i or .swg suffix, containing the ANSI C/C++ declarations of the function we want to export as a Grid service and special SWIG directives. We must create this input file according the prototype of the C function we want to export.  For the getResult() function, we will have the following getResult.i input file:

%module ibm
char * getResult(int,char*);

The name of the module is supplied using the special %module SWIG directive. Subsequently, two Java wrapper classes will be generated by SWIG, ibm and ibmJNI.  ibmJNI class exposes a static method that invokes the native implementation.  ibm class is an example that shows how to use ibmJNI.  Our grid service will use the ibmJNI class to invoke the getResult() C function.

In order to generate the Java wrapper classes, we invoke the swig command with the –java flag as follows:

swig –java getResult.i

This command generates three files ibm.java, ibmJNI.java and getResult_wrap.c.

If we look at ibm.java, we can see that the call to the static member ibm.getResult() will invoke the C function.  Note also that the String Java class is mapped to the char* C type.

public class ibm {
  public static String getResult(int arg0, String arg1) {
    return ibmJNI.getResult(arg0, arg1);
  }
}

Compiling and installing the JNI wrappers

The way JNI works is that the Java runtime will load dynamically the native C shared library that implements getResult().  This shared library needs to be present where the grid service will be instantiated.  For a grid service hosted by the IBM Grid Toolbox, this shared library must be stored in the /opt/IBMGrid/lib directory.  Check that the LD_LIBRARY_PATH environment variable is set to this path when starting the IBM Grid Toolbox container.

In order to create the shared library implementing getResult(), we issue the command:

gcc –f pic -I /opt/IBMGrid/AppServer/java/include/ -shared -o libgetResult.so getResult.c getResult_wrap.c

The generated C wrapper getResult_wrap.c includes the jni.h header that can be found in the /opt/IBMGrid/AppServer/java/include/ directory.  The library libgetResult.so must be copied in /opt/IBMGrid/lib.

Then the following Java code can be used to invoke the C function.  It must import the ibmJNI class automatically generated by SWIG:

import ibmJNI;
 
public class main {
 
  static {
    try {
        System.loadLibrary("getResult");
    } catch (UnsatisfiedLinkError e) {
        System.err.println("Native code library failed to load. See the
        chapter on Dynamic Linking Problems in the SWIG Java documentation for
        help.\n" + e);
        System.exit(1);
    }
  }
 
  public static void main(String argv[]) {
        String c="Hello";
        System.out.println("The resulting string is " +                                             ibmJNI.getResult(10,c));
  }
}

System.loadLibrary("getResult") tells Java to load the libgetResult.so dynamic library.  If this library is not installed in the appropriate location, an exception will be caught and the program will abort.  Be cautious, and test with a sample program like the previous one to make sure no errors occur at runtime.  It will be more difficult to debug problems located in the library when the service will be deployed because errors with be interpreted as SOAP messages and no relevant messages will allow you to understand what the problem is.

Finally, ibmJNI.getResult() is called to invoke the C implementation.

To test if your JNI call works, issue the following commands:

javac main.java
java main
 
The resulting string is “HelloHelloHelloHelloHelloHelloHelloHelloHelloHello

Note that the Java environment is setup by invoking the following script provided by the IBM Grid Toolbox:

/opt/IBMGrid/igt-setenv.sh

Creating and deploying the grid service

You can deploy a grid service as a GAR file.  The generation of the GAR file from a Java class is performed through the following six steps:

1.       Create a Java file that implements the methods you want to export as a grid service.  Use a java package for this purpose.

2.       Create a JAR file containing all the necessary compiled Java class files

3.       Generate the grid service Java class implementation (including the Factory class that will instantiate the grid service) by using the Apache ant build tools against the IBM Grid Toolbox /opt/IBMGrid/build-tools.xml build description file

4.       Build the deployable GAR file by using ant and the /opt/IBMGrid/build-tools.xml file

5.       Deploy the GAR file into the IBM Grid Toolbox container

6.       Stop and restart the IBM Grid Toolbox container.

Note that because of our C implementation, an additional step is required to install the shared library on each node where the grid service is to be instantiated.  This step can be achieved easily and accurately by creating a simple RPM package.

Creating the grid service

The Java class that will call the C getResult() function can simply be written as:

import ibmJNI;
 
public class CFunc {
 
  static {
    try {
      System.loadLibrary("getResult");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load" + e);
      System.exit(1);
    }
  }
 
  public String getResult(int n,String s) {
      return ibmJNI.getResult(n,s);
  }
}

This class will expose its methods as grid services. However to make the IBM Grid Toolbox tooling work properly, it is better to use a Java package, as follows. 

Using a Java package

Using the Java Package MyPackage, CFunc.java and ibmJNI.java then become:

package MyPackage;
 
import MyPackage.ibmJNI;
 
public class CFunc {
 
  static {
    try {
        System.loadLibrary("getResult");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load" + e);
      System.exit(1);
    }
  }
 
  public String getResult(int n,String s) {
         return ibmJNI.getResult(n,s);
  }
}

and

package MyPackage;
class ibmJNI {
     public final static native String getResult(int jarg1, String jarg2);
}

getResult() is now known as MyPackage.ibmJNI.getResult() so the getResult_wrap.c file must be slightly modified after it has been generated by SWIG. In order to properly export getResult() we need to change the prototype and to include the name of the java package:

from

 
JNIEXPORT jstring JNICALL Java_ibmJNI_GetResult(JNIEnv *jenv, jclass jcls, jint jarg1, jstring jarg2) {

to

JNIEXPORT jstring JNICALL Java_MyPackage_ibmJNI_GetResult(JNIEnv *jenv, jclass jcls, jint jarg1, jstring jarg2) {

The shared library must be recompiled and redeployed using the commands:

gcc –f pic -I /opt/IBMGrid/AppServer/java/include/ -shared -o libgetResult.so getResult.c getResult_wrap.c
cp libgetResult.so /opt/IBMGrid/lib

Building the service

First, we build the JAR file for this implementation, using the commands:

javac MyPackage/ibmJNI.java
javac MyPackage/CFunc.java
jar cvf CFunc.jar  MyPackage/ibmJNI.class MyPackage/CFunc.class
 

Then, by using the IBM Grid Toolbox build tools, we can easily generate the GAR file necessary to the grid service deployment. We must first create a build directory, /tmp/BUILD in our example.

We create the grid service Java classes:

ant -f /opt/IBMGrid/build-tools.xml createBottomUpGridService -verbose -DoutputDir=/tmp/BUILD -DbottomUp.sourceJar=${PWD}/CFunc.jar -DbottomUp.classOfPortType=MyPackage.CFunc

MyPackage.CFunc is the name of the Java class whose methods will be exported as grid services.

${PWD}/CFunc.jar is the implementation of this class.

The build process takes about ten seconds.  Having a look at the /tmp/BUILD/MyPackage directory, we notice that two Java classes have been created CFuncProvider and CFuncPortType.  CFuncProvider provides the getResult() method.

And we build the GAR file:

ant -f /opt/IBMGrid/build-tools.xml gar -DoutputDir=/tmp/BUILD/ -Djar.name=CFuncservice.jar -Dgar.name=CFuncservice.gar

As in the previous build process, the same temporary build directory must be provided.

Two new files (whose filenames were passed as parameters) are then created in the build directory:

-          CFuncservice.gar that will be used next to deploy the service into the IBM Grid Toolbox for Mutliplatform container

-          CFuncservice.jar that will be used by the client to invoke the grid service.

Deploying the grid service

Using the IBM Grid Toolbox for Multiplatform container, it is to deploy the grid service. We just go to the build directory and issue the following commands:

igt-undeploy-gar CFuncservice
igt-deploy-gar CFuncservice.gar
igt-stop-container
igt-start-container

We can check that our grid service has been successfully deployed by accessing the IBM Grid Toolbox Grid Services Manager at the following url: http://<igt_hostname>:12080/gsm/DLogon.jsp. We log in, create a proxy, click on “Manage Available Instances”, then “ogsa” and “Manage Grid services”.  Our CFuncProvider Factory is there. 

services/MyPackage/CFuncProviderFactoryService should be inactive.  When started, it will create the CFunc service instance.

In /opt/IBMGrid/AppServer/installedApps/DefaultNode/IBMGrid.ear/ogsa.war/WEB-INF/lib, we can check that CFuncservice.jar is there.  That means that our service is able to get instantiated.

Using the grid service

To test our grid service, we need to create an instance of this service.  The way to do it is to ask the factory to perform the CFunc grid service creation.  Then, we will invoke the grid service directly.

We can start two instances of our service by using the ogsi-create-service command, as follows:

ogsi-create-service http://<ipaddress>:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService MyFirstCFuncInstance
ogsi-create-service http://<ipaddress>:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService MySecondCFuncInstance

http://<ipaddress>:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService is the Grid Service Handle (GSH) of the Factory service used to create our CFunc grid service instance.  If you remember, this Factory was automatically generated by the IGT tooling.  MyFirstCFuncInstance  and MySecondCFuncInstance are  two arbitrary names for these instances.  The Grid Service Handles for these grid service instances will respectively be:

-                 http://<ipaddress>:12080/ogsa/services/services/MyPackage/

-                 CFuncProviderFactoryService/MyFirstCFuncInstance
-                 http://<ipaddress>:12080/ogsa/services/services/MyPackage/

-                 CFuncProviderFactoryService/MySecondCFuncInstance

We can check that these two instances have been started accessing again the IGT Grid Services Manager:

 

Now, we can use the following code sample to invoke our grid services:

import java.net.URL;
import java.util.Calendar;
 
import org.globus.ogsa.utils.GridServiceFactory;
import org.globus.ogsa.wsdl.GSR;
import org.gridforum.ogsi.Factory;
import org.gridforum.ogsi.GridService;
import org.gridforum.ogsi.HandleType;
import org.gridforum.ogsi.LocatorType;
import org.gridforum.ogsi.OGSIServiceGridLocator;
 
import MyPackage.service.CFuncServiceGridLocator;
import MyPackage.CFuncPortType;
 
public class test_getResult {
 
        public static void main(String[] args) {
               CFuncPortType service = null;
               try {
               URL GSH = new java.net.URL(args[0]);
               CFuncServiceGridLocator locator = new CFuncServiceGridLocator();
               service = locator.getCFuncPort(GSH);
               
               int i;
 
               for ( i=1; i<=30;i++) {
                       System.out.println( "Calling the service: ");
                       String res = service.getResult(i,"Hello");
                       System.out.println( "res = " + res );
               };
 
               } catch (Exception e) {
                       System.out.println( "\n Destroying the service: ");
                       try  {
                               service.destroy();
                               System.out.println( "Service Destroyed");
                       } catch (Exception f) {
                       };
               }
        }
}

From the GSH provided as parameter, we can create a CFuncPortType object that provides the getResult() method.  We use a CFuncServiceGridLocator object to get to the CFuncPortType object.  Note that both CFuncPortType and CFuncServiceGridLocator classes were automatically generated from our CFunc class by the IBM Grid Toolbox tooling.

In order to compile this service client code, we must first import the stub classes generated by the IBM Grid Toolbox tooling, as follows:

export CLASSPATH=/tmp/BUILD/CFuncservice.jar:$CLASSPATH
javac test_getResult.java

To invoke the grid service, we need to specify the GSH of the grid service as command line argument to the service client.  The GSH is the GSH of the Factory followed by the name of the service instance.  The getResult() function will be called 30 times in the sample client program.  That shows that once the CFuncServiceGridLocator object and our associated service have been created, the function can be called repeatedly.

[root@gdcmop23 mytest]# java test_getResult http://129.40.153.87:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService/MyFirstCFuncInstance
OGSALogFactory Looking for file : /opt/IBMGrid/AppServer/installedApps/DefaultNode/IBMGrid.ear/ogsa.war/WEB-INF/ogsilogging.properties
OGSALogFactory Looking for file : /opt/IBMGrid/AppServer/installedApps/DefaultNode/IBMGrid.ear/ogsa.war/WEB-INF/ogsilogging.properties
Calling the service:
res = Hello
Calling the service:
res = HelloHello
Calling the service:
res = HelloHelloHello
Calling the service:
res = HelloHelloHelloHello
Calling the service:
res = HelloHelloHelloHelloHello
Calling the service:
res = HelloHelloHelloHelloHelloHello
Calling the service:
res = HelloHelloHelloHelloHelloHelloHello
Calling the service:
res = HelloHelloHelloHelloHelloHelloHelloHello

Finally, we can destroy the two service instances (as they were started to run indefinitely) by using the ogsi-destroy-service command against the GSH of each grid service instance:

ogsi-destroy-service  http://<ipaddress>:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService/MyFirstCFuncInstance
ogsi-destroy-service http://<ipaddress>:12080/ogsa/services/services/MyPackage/CFuncProviderFactoryService/MySecondCFuncInstance

 

Conclusion

This article explains how the Java JNI technology can be used to integrate a legacy C function into a grid infrastructure built on top of the IBM Grid Toolbox. We illustrated how the tools provided by IGT can be used to ease grid service development and deployment. Grid services provide several useful capabilities not covered in this article. The authors strongly encourage the readers to experiment with them by installing the IBM Grid Toolbox on their laptops and developing their own grid services.

Resources

·         To know how to use the IBM Grid Toolbox, check the InfoCenter at http://publib.boulder.ibm.com/eserver/v1r1/fr_FR/index.html

·         For information on JNI, see http://java.sun.com/j2se/1.3/docs/guide/jni/

·         For information about the SWIG tools, see http://www.swig.org/Doc1.3/Java.html#n30

·         For more information about IBM and Grid Computing, see http://www.ibm.com/grid

·         For information on the GlobusToolkit, see http://www-unix.globus.org/toolkit/

About the authors


Jean-Yves Girard is an IT Specialist working in the EMEA Design Center for ebusiness on demand, IBM Server Group, located in Montpellier, France. He has been involved with Grid Computing since March 2002 and Linux solutions for four years. His areas of expertise include Linux operating system, HPC computing, Grid technologies and Web Services. You can reach Jean-Yves at girardjy@fr.ibm.com.

 

Ioane Muni Toke is a Ph.D. student in Applied Mathematics and Computer Science at the Ecole Centrale Paris. He is currently working on the gridification of several financial stochastic algorithms. His work is supported by the IBM PhD Fellowship Program.

 

Jean-Luc Lepesant is a Software Engineer working in the EMEA Design Center for e-business on demand, IBM Server Group, located in France in Montpellier. He has worked for IBM for 25 years and been involved with grid computing since January 2003. His areas of expertise include application development with Java and C language.

After working at IBM Research for 13 years on parallel I/O and grid computing, Jean-Pierre Prost joined in mid 2002 the EMEA Design Center for ebusiness on demand in Montpellier to lead the technical activities around Grid Computing. He is at the origin of the IBM intraGrid project, aimed at deploying an enterprise Grid within IBM, and is the lead architect of the Grid Design Center Grid, interconnecting the IBM Design Centers for ebusiness on demand across the world.