Writing CCA Components Using XCAT-Java


CONTENTS


INTRODUCTION

This tutorial gives an overview of how to write CCA Components using the XCAT-Java toolkit. We also show these components can be made compatible with the Open Grid Services Infrastructure (OGSI), with the help of the XSOAP-GSX toolkit.

For an overview of the XCAT project, visit our webpage here. Interested readers are also encouraged to visit our Grid Web Services project pages here.

REQUIREMENTS

INSTALLATION

WRITING SIMPLE COMPONENTS

In this section we describe how to write two components, one providing a service and another using it. The service being provided is a simple convertor of temperature from celsius to fahrenheit scales. Although this is not a typical use-case for XCAT-Java, the same concepts will apply even if more complicated components are to be written. The sources for this example are in the $XCAT_HOME/src/java/samples directory. The interfaces are in the idl subdirectory, while the components themselves are present in the convert/simple subdirectory.

Writing Port Interfaces

We begin by writing the interface definitions for the Ports that are used. This sample uses two types of ports: a Convert Port which provides the temperature conversion service, and a CCA-defined GoPort which is used to bootstrap the execution of the components. We describe only the former in detail, since the latter is along the same lines.

Convert_idl.java (which can be found in directory $XCAT_HOME/src/java/samples/idl/convert) defines the interface for the temperature service.

public interface Convert_idl extends xcat.framework.ccacore.Port {

    /**
     * Method to convert a value in celsius to centigrade
     */
    public float centigradeToFahrenheit(float celcius)
        throws soaprmi.RemoteException;
}
As can be inferred, all port interfaces in XCAT-Java inherit from xcat.framework.ccacore.Port, and all methods are supposed to throw soaprmi.RemoteException since they are expected to be invoked remotely.

Apart from the port interface itself, two more classes ProvidesConvert and UsesConvert need to be created, for the Provides and Uses side respectively.

public interface ProvidesConvert extends Convert_idl,
                                         xcat.framework.ccacore.ProvidesPort {
}

public interface UsesConvert extends Convert_idl,
                                     xcat.framework.ccacore.UsesPort {
}
Note that they extend the ProvidesPort and UsesPort interfaces respectively. Ideally, these classes will be automatically generated, but currently we don't provide any code generation.

Implementing the Component and Ports

We describe how to write two components: ProvidesConvertComponent and UsesConvertComponent. As the names suggest, the former provides the temperature conversion service, while the latter uses it. Also the temperature conversion service is itself provided by the implementation of the Convert Port. All sources are present in the subdirectory $XCAT_HOME/src/java/samples/convert/simple.

We will first look at the implementation of the Convert Port, as provided in ConvertImpl.java. The javadoc style comments explain some important details.

/**
 * Every port implementation needs to extend from UnicastRemoteObject,
 * and implement the interface defined by the ProvidesPort
 */
public class ConvertImpl extends soaprmi.server.UnicastRemoteObject
    implements samples.idl.convert.ProvidesConvert {

    /**
     * Default constructor. Since the Object extends a UnicastRemoteObject,
     * it is automatically exported as an XSOAP service by the superclass
     * constructor. RemoteException is thrown by the superclass constructor.
     */
    public ConvertImpl() throws RemoteException {
    }


    /**
     * The implementation of the conversion operation
     */
    public float centigradeToFahrenheit(float celcius)
        throws RemoteException {
        float fahr = ( ( 9f / 5f ) * celcius ) + 32;
        return fahr;
    }
}
Similarly, the GoPortImpl is also defined in order to stop and start the components.

Next, we need to write the two components themselves. We begin with the ProvidesConvert Component.

public class ProvidesConvertComponent
    implements xcat.framework.ccacore.Component {

    // instance of the services object - every CCA component needs one
    private xcat.framework.ccacore.Services providesConvertCore;

    // instance of the convert port
    private ConvertImpl convertImpl;

    /**
     * Default empty constructor: Required
     */
    public ProvidesConvertComponent() {
    }

    ....
}
Every component needs to implement the Component interface. Every component also needs a Services object. An empty constructor is also required since the XCAT Component Instantiator tries to create an instance of a new component by calling its empty constructor using Java reflection.

Next, the setServices method defined by the Component interface has to be defined. This is where all ports (uses and provides) are added. Again, the comments explain some important details.

    public void setServices(Services cc) {
        try {
            // Set the Services object
            providesConvertCore = cc;

            // Adding a mapping for a Provides Port
            // Accepts the unique name for the port, the namespace,
            // and the class object for the Provides Port interface
            ProvidesPortMapping.addMapping
                ("convertProvidesPort",
                 "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#simple",
                 new Class[] {ProvidesConvert.class}
                 );

            // Instance of convert provides port
            // Use the same values as above
            convertImpl = new ConvertImpl();
            providesConvertCore.addProvidesPort
                (convertImpl,
                 new PortInfoImpl
                     ("convertProvidesPort",
                      "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#simple")
                     );
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Unable to setServices: " + e);
        }
    }

Similarly, the UsesControl Component is also defined. However, there are some differences in the setServices method, since the component has different ports. In particular, here is how a Uses Port is added.

            // Adding a UsesPort Mapping for UsesConvert port
            // Accepts the unique name for the port, the namespace,
            // and the class object for the Uses Port interface
            UsesPortMapping.addMapping
                ("convertUsesPort",
                 "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#simple",
                 new Class[] {UsesConvert.class}
                 );

            // standard cca call to register uses port
            // Use the same values as above
            usesConvertCore.registerUsesPort
                (new PortInfoImpl
                    ("convertUsesPort",
                     "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#simple")
                );
In addition, the UsesControl component also defines the startExecuting method that is called by the go method of the GoPort. This method gets a reference to a registered uses port, and invokes remote methods on it. The following code from the startExecuting method and corresponding comments illustrate this.
            // Get a reference to a UsesConvert port
            // Use the same name that is used to register the Port
            // The getPort method returns a reference only if a CCA
            // connection has been made between this uses port and
            // a provides port of the same type, and the same uses
            // port is not being used by another thread
            UsesConvert usesConvert = (UsesConvert)
                usesConvertCore.getPort("convertUsesPort");

            // Make a remote call on the ProvidesConvert port,
            // via the UsesConvert port
            System.out.println("UsesConvertComponent: Celcius : 0, Fahrenheit: " +
                               usesConvert.centigradeToFahrenheit(0f));

            // Release the port when you are done using
            // If it is not released, another getPort with the
            // same arguments will block
            usesConvertCore.releasePort("convertUsesPort");

Running the Components

XCAT Components can be created, connected and remotely invoked using the Jython user-interface provided. For a tutorial on how to use the Jython interface, click here.

The Jython script for the components in this tutorial can be seen at $XCAT_HOME/src/python/samples/xcatTutorial.py. To run the tutorial, change to directory $XCAT_HOME, and type run.sh script ./src/python/samples/xcatTutorial.py.

The following Jython code and associated comments illustrate how the components are created, connected, and set in motion.

# Create an environment object for the UsesConvertComponent
usesComponent = EnvObj()
usesComponent.put("component-type", 
		  "java")
usesComponent.put("exec-dir", 
		  xcatHome + "/src/java/scripts")
usesComponent.put("exec-script", 
		  "JavaComponentScript.sh")
usesComponent.put("exec-name", 
		  "usesConvertComponent")
usesComponent.put("exec-fqn", 
		  "samples.convert.simple.UsesConvertComponent")

# Create an environment object for the ProvidesConvertComponent
providesComponent = EnvObj()
providesComponent.put("component-type", 
		      "java")
providesComponent.put("exec-dir", 
		      xcatHome + "/src/java/scripts")
providesComponent.put("exec-script", 
		      "JavaComponentScript.sh")
providesComponent.put("exec-name", 
		      "providesConvertComponent")
providesComponent.put("exec-fqn", 
		      "samples.convert.simple.ProvidesConvertComponent")
The above snippet sets the environment for execution of the components, e.g it tells the system that the directory for execution is $XCAT_HOME/src/java/scripts (using the exec-dir variable) and the script that is called to instantiate the component is JavaComponentScript.sh. Note that this information pertains to the location where the component is to be instantiated. In other words, if the component is to be instantiated locally then the above information should be valid for the local machine, and if the component is to be instantiated remotely the information should be valid on the remote machine. Make sure that the JDK variable is set correctly in the JavaComponentScript.sh to point to a valid Java installation on the target machine.
# create component wrappers
uses = cca.createComponent(usesComponent)
provides = cca.createComponent(providesComponent)

# assign a machine name
usesmc = "k2.extreme.indiana.edu"
providesmc = "k2.extreme.indiana.edu"
cca.setMachineName(uses, usesmc)
cca.setMachineName(provides, providesmc)

# set a creation mechanism to be local (in the same process)
cca.setCreationMechanism(uses, "local")
cca.setCreationMechanism(provides, "local")
The above snippet tells the system that the components are to be launched in the same process. If these components have to be launched remotely, then set the creation mechanism to be ssh [or gram if you want to use Globus GRAM to launch your jobs. However, since this is a basic tutorial, we don't provide any instructions on how to use our system with Globus; but if you are interested in doing so, drop us a note].
# set a creation mechanism to be ssh (as described above)
cca.setCreationMechanism(uses, "ssh")
cca.setCreationMechanism(provides, "ssh")

Additionally, you will also have to tell the system where to find the ssh installation, and you can do that by adding it as environment variables as follows:

providesComponent.put("local-ssh-path", 
		      "/usr/local/bin/ssh")
usesComponent.put("local-ssh-path",
                  "/usr/local/bin/ssh")
If you are using ssh as the creation mechanism, the stdout and stderr for the components can be seen as .out and .err files in the directory pointed to by the exec-dir variable on the target (remote) machine. Another thing to note is that CTRL-C does not kill the remote components, it only terminates the local jython interpreter. Hence, you will have to kill your components manually when you are done. In general, it is good practice to add a kill method to one of your ports so that you can terminate all remote components by invoking it before you explicitly exit.

The following code snippet creates live instances of the components on the target machines using the specified creation mechanism, connects the ports, and bootstraps the execution of the application (by invoking the go method on the GoPort implementation of the uses-component).

# create live instances
cca.createInstance(uses)
print "Jython script: Created the uses component"
cca.createInstance(provides)
print "Jython script: Created the provides component"

# connect the convertUsesPort of the UsesComponent to the
# convertProvidesPort of the ProvidesComponent
cca.connectPorts(uses, "convertUsesPort", provides, "convertProvidesPort")

# start the components using the go method of the GoPort
usesGoPortClassName = "samples.idl.goPort.UsesGoPort"
usesPortType = "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#simple"
providesPortName = "providesGoPort"
methodName = "go"
methodParams = zeros(0, Object)

print "Jython script: Invoking method on uses's go port"
cca.invokeMethodOnComponent(uses, usesGoPortClassName, usesPortType, providesPortName, methodName, methodParams)
print "Jython script: Components set in execution"
For more information on the possible values for some of the variables (e.g creationMechanism, valid name-value pairs) click here.

WRITING OGSI COMPATIBLE COMPONENTS

In this section, we extend the above components so that they are compatible with the OGSI specification, and interoperable with other implementations of OGSI, such as GT3. As metioned before, XCAT-Java uses the XSOAP-GSX toolkit for OGSI Compatibility. Readers are encouraged to read the tutorial on the GSX website to familiarize themselves with it.

Writing Port Interfaces

Every Provides Port in XCAT-Java is exposed as a Grid service. Thus, every component is a set of OGSI-compatible Grid services. Continuing with the same example as above, we have extend the ProvidesConvert and UsesConvert classes (in the $XCAT_HOME/src/java/samples/idl/convert directory) as follows:

public interface ProvidesOGSIConvert extends ProvidesConvert,
       soaprmi.ogsi.xsoap_interface.XSoapGridServiceInterface {
}
public interface UsesOGSIConvert extends UsesConvert,
       soaprmi.ogsi.xsoap_interface.XSoapGridServiceInterface {
}
The XSoapGridServiceInterface is the interface defined by GSX to define the GridServicePort, which is specified by OGSI as the portType to be implemented by all Grid services.

We also do the same process for the other port, GoPort, to make it OGSI compatible.

Implementing the Component and Ports

We describe how to write two OGSI compatible components: ProvidesOGSIConvertComponent and UsesOGSIConvertComponent, and also the implementation of the OGSI compatible ports OGSIConvertImpl and OGSIGoPortImpl. Most of the implementation is similar to that of the simple components; however there are some differences that we point out in this section. All sources are present in the subdirectory $XCAT_HOME/src/java/samples/convert/ogsi.

We start with the implementation of the Ports. As an example, we will look at OGSIConvertImpl. The java documentation in the following code illustrates how to write an OGSI-compliant Port implementation.

/**
 * The port implementation has to extend from the basic GridServiceImpl
 * provided by GSX. GridServiceImpl implements most methods defined by the
 * XSoapGridServiceInterface, and expects the user to implement the
 * destroyImpl method for destruction of the service
 */
public class OGSIConvertImpl extends soaprmi.ogsi.service.grid_service.GridServiceImpl
    implements ProvidesOGSIConvert {
    
    /**
     * Default constructor.
     */
    public OGSIConvertImpl() throws Exception {
        super();

        // Export this object explicitly - GSX doesn't do this automatically
        // The exportObject exposes this object as a remote service
        UnicastRemoteObject.exportObject
            (this,
             new Class[]{ProvidesOGSIConvert.class});
    }

    /**
     * Method to convert a value in celcius to centigrade
     * This is the same as the simple example 
     */
    public float centigradeToFahrenheit(float celcius) 
        throws RemoteException {
        float fahr = ( ( 9f / 5f ) * celcius ) + 32;
        return fahr;
    }

    /**
     * Destroy inherited from GridServiceImpl
     * In this case, we refuse to destroy the service
     */
    public void destroyImpl() {
        // port won't be destroyed
        return;
    }
}

Next, we write the two components themselves: ProvidesOGSIConvertComponent and UsesOGSIConvertComponent. This is practically the same as the implementation of the simple components above, except that the ports that are registered and added are the ports that we just made OGSI-compatible. For instance, in the ProvidesOGSIConvertComponent, the ProvidesOGSIConvert port is added as follows:

            // Almost same as simple case, but note the change in the class
            // specified as the ProvidesPort interface
            ProvidesPortMapping.addMapping
                ("convertProvidesPort",
                 "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#ogsi",
                 new Class[] {ProvidesOGSIConvert.class}
                );

            // Instance of convert provides port
            // Note that the instance of the Port created is of type OGSIConvertImpl
            convertImpl = new OGSIConvertImpl();
            providesConvertCore.addProvidesPort
                (convertImpl,
                 new PortInfoImpl
                     ("convertProvidesPort",
                      "http://www.extreme.indiana.edu/xcat/samples/convert/wsdl#ogsi")
                     );
For more details, please look at the sources available in subdirectory $XCAT_HOME/src/java/samples/convert/ogsi.

Running the Components

The ogsiTutorial.py script located in $XCAT_HOME/src/python/samples can be used to create, connect and bootstrap execution of the above OGSI-enabled components. This script is very similar to the xcatTutorial.py script, except that the ports, components, and classes that are being referred to are the ones belong the OGSI-compatible versions. However, there are a few details that we would like to draw your attention to.

For both the components the creation mechanism is set to exec (and not local). Also, the exec-script name is set to value OGSIComponentScript.sh (and not JavaComponentScript.sh). The reasons for this are related. The exec creation mechanism starts the component as a separate process, with a call to the script defined by the exec-script. In case the local mechanism is used for creation, the components are created as new objects in the same process space, and hence the exec-script variable is ignored. The OGSIComponentScript.sh sets appropriate classpaths and executes the generic OGSIComponentInstantiator which not only creates an instance of the appropriate component, but also adds another a ComponentGridService port automatically to each component. This port functions as a manager for the Component. It contains Service Data Elements (SDEs) with the Grid Service Handles (GSH) for all the ports belong to the component. Thus, this port can be queried to figure out the handles for the different Provides Ports that belong to the Component.

To run the components, change to the directory $XCAT_HOME, and type run.sh script $XCAT_HOME/src/python/samples/ogsiTutorial.py. Since the components run in separate address spaces, their standard outputs and errors don't appear on the screen, and are dumped to .err and .out files in the $XCAT_HOME directory. For instance, the providesOGSIConvertComponent.out may look like this:

ProvidesOGSIConvertComponent: Added provides port mapping
ProvidesOGSIConvertComponent: Adding provides port of type ProvidesOGSIConvert
Provides Port componentGridServicePort Location : http://129.79.246.109:48721/GUID_1068671974609_266096990_1
Provides Port convertProvidesPort Location : http://129.79.246.109:48721/GUID_1068671975069_325540038_3
OGSIConvertImpl: Received celcius value: 0.0
OGSIConvertImpl: Returning fahrenheit value: 32.0
OGSIConvertImpl: Received celcius value: 20.0
OGSIConvertImpl: Returning fahrenheit value: 68.0
OGSIConvertImpl: Received celcius value: 40.0
OGSIConvertImpl: Returning fahrenheit value: 104.0
OGSIConvertImpl: Received celcius value: 60.0
OGSIConvertImpl: Returning fahrenheit value: 140.0
OGSIConvertImpl: Received celcius value: 80.0
OGSIConvertImpl: Returning fahrenheit value: 176.0
OGSIConvertImpl: Received celcius value: 100.0
OGSIConvertImpl: Returning fahrenheit value: 212.0
The FindServiceDataByName utility provided by XCAT-Java can be utilized to find the handles of the Provides Ports by invoking it using the handle (see above) of the ComponentGridService Port as follows:
srikrish@k2:~> cd $XCAT_HOME
srikrish@k2:~/Projects/xcatjava> run.sh xcat.framework.ogsi.FindServiceDataByName http://129.79.246.109:48721/GUID_1068671974609_266096990_1 http://www.extreme.indiana.edu/xcat/framework/ogsi/componentGridServicePort providesPortHandle
The following output should be seen:

Executing query: 
<ogsi:queryExpression xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI">
    <ogsi:queryByServiceDataNames xmlns:com="http://www.extreme.indiana.edu/xcat/framework/ogsi/componentGridServicePort">
       <ogsi:name>com:providesPortHandle</ogsi:name>
    </ogsi:queryByServiceDataNames>
</ogsi:queryExpression>

Query Result: 
<?xml version="1.0" encoding="UTF-8"?>
<ogsi:result xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <sd:serviceDataValues xmlns:sd="http://www.gridforum.org/namespaces/2003/03/serviceData">
    <tns:providesPortHandle name="componentGridServicePort" xmlns:tns="http://www.extreme.indiana.edu/xcat/framework/ogsi/componentGridServicePort">http://129.79.246.109:48721/GUID_1068671974609_266096990_1</tns:providesPortHandle>
    <tns:providesPortHandle name="convertProvidesPort" xmlns:tns="http://www.extreme.indiana.edu/xcat/framework/ogsi/componentGridServicePort">http://129.79.246.109:48721/GUID_1068671975069_325540038_3</tns:providesPortHandle>
  </sd:serviceDataValues>
</ogsi:result>

The output shows two Provides Ports with their respective handles. These should be the same as the ones seen in the providesOGSIConvertComponent.out file.

FEEDBACK

We are very excited that you got this far! Please feel free to send any comments and/or suggestions to our mailing list. We will be more than happy to hear from you.
Sriram Krishnan
Last modified: Fri Feb 6 14:01:12 EST 2004