Core: Developer's Guide

Overview
APIs
Programmer's Tutorial
Java Programmer's Guide: Getting Started
Java Programmer's Guide: Additional APIs
>C Programmer's Guide
Performance Guide
Security Support
Test Framework

C Programming Guide

This document is a guide to writing C clients which interact with Grid Services. It covers the Core framework, Security, and the GRAM client wrapper API.

Basic knowledge of C is assumed in this guide. We also assume that you are familiar with the basic OGSA environment described in the <User's Guide>. The gSOAP User's Guide will also be a valuable reference.

Download

Bundles

Please refer to the Globus Toolkit download page for further information on downloading the latest release bundles. In order to browse the source of the OGSA-C packages from the GT3 installer, go to the gt3-installer/BUILD directory. The packages that make up the OGSA-C bundle are listed here:

Packages used for parsing/handling XML:

  • globus_libxml2
  • globus_libxslt

gSOAP packages:

  • globus_gsoap_soapcpp2
  • globus_gsoap_stdsoap2
  • globus_gsoap_io_plugin
  • globus_gsoap_error_plugin

Utils packages:

  • globus_ogsa_utils
  • globus_ogsa_utils_error

OGSI Core packages:

  • globus_ogsi_core
  • globus_ogsi_notification
  • globus_ogsi_service_plugin
  • globus_ogsi_types
  • globus_ogsi_types_bindings

Security packages:

  • globus_grim_devel
  • globus_ogsa_security_authentication
  • globus_ogsa_security_authentication_bindings
  • globus_ogsa_security_grim
  • globus_ogsa_security_rsa_sha1
  • globus_ogsa_security_wsse_gssapi
  • globus_xmlsec

Samples:

  • globus_ogsa_samples_counter_bindings
  • globus_ogsa_samples_counter_test

GRAM Client Packages:

  • globus_gram_xml_rsl
  • globus_gram_xml_rsl_test
  • globus_gt3_gram_client
  • globus_gt3_gram_client_test
  • globus_ogsa_base_gram_managed_job
  • globus_ogsa_base_gram_mj_bindings
  • globus_ogsa_base_gram_mj_test
  • globus_ogsa_base_gram_mj

CVS

To get the latest OGSA-C source code from CVS, use the module name "ogsa-c", and follow the instructions on the CVS Howto.

You can build the source from CVS using the ogsa-c/build_tools/cvs-build-ogsa script. Usually you can just run:

./cvs-build-ogsa gcc32dbg

The -help option gives further information about using that script.

There a few pre-requisites before running the script:

  • The globus versions of autotools must be installed and at the front of your path. See the CVS Howto for further information on how to accomplish this.
  • GPT must be installed, and GPT_LOCATION set. See Installing GPT for further information.
  • You must have at least a 2.4.1 version of the Globus Toolkit (version 2) installed, with GLOBUS_LOCATION set to that installation. Version 2.4.1 comes with the GT3 final release, or it can be downloaded and installed separately. See the Globus Toolkit Releases page for further info.

Writing a Client

The following steps are involved in writing a C client:

Step 1. Provide a gSOAP Service Description File
Step 2. Creating a GPT package from the gSOAP Description
Step 3. Generate Support Code

Provide a gSOAP Service Description File
1

The C client framework uses gSOAP as the underlying SOAP implementation. gSOAP generates client side stubs for a service from a gSOAP definition file (usually suffixed as ".gsoap"). The definition file has similar formatting to a C header file, but it should not be included in any C code or compiled by a C compiler. Consider it a separately formatted schema file similar in function to WSDL. Here is an example gSOAP definition file for a simple counter service, included in the GT3 distribution. This coincides with the GWSDL schema file for the counter service. See the gSOAP documentation for complete documentation on the formatting of the gSOAP definition file. A few OGSA-C specific features of the gSOAP definition file should be noted:

Namespaces must be defined in an associated namespace source file. The namespaces for the counter example (defined in globus_ogsa_samples_counter_bindings_namespaces.c) are as follows:

#include <stdsoap2.h>

struct Namespace globus_ogsa_samples_counter_bindings_namespaces[] =
{
    {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2002/06/soap-encoding"},
    {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/2002/06/soap-envelope"},
    {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance"},
    {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema"},
    {"ogsi", "http://www.gridforum.org/namespaces/2003/03/OGSI"},
    {"gwsdl", "http://www.gridforum.org/namespaces/2003/03/gridWSDLExtensions"},
    {"sd", "http://www.gridforum.org/namespaces/2003/03/serviceData"},
    {"counter", "http://ogsa.globus.org/samples/counter"},
    {NULL, NULL}
};

Notice that the namespace prefixes correspond to the prefixes for type definitions and function declarations in the associated gSOAP definition file. When writing the support code for a grid service client, the gSOAP handle must be initialized with the appropriate namespaces array:

extern struct Namespace globus_ogsa_samples_counter_bindings_namespaces[];
#define COUNTER_DEFAULT_NS "http://ogsa.globus.org/samples/counter"
...

globus_ogsa_utils_gsoap_handle_init(
    &gsoap_handle,
    COUNTER_DEFAULT_NS,
    1,
    globus_ogsa_samples_counter_bindings_namespaces);

See the counter example in the GT3.2 distribution for further documentation.

Service Data for a grid service must be defined using the "gridService" operator as follows:

gridService struct counter__CounterService_s
{
    _counter__counter *                 counter;
} counter__CounterService;

This is similar to a "typedef", but the stub generator includes additional service data handling functions when it sees the "gridService" operator. The functions generated for this type are:

int globus_counter__CounterService_s_deserialize(
    struct soap *                       soap,
    const char *                        sde_name,
    void *                              service_handle);


int globus_counter__CounterService_s_serialize(
    struct soap *                       soap,
    void *                              service_handle);


void * globus_counter__CounterService_s_init(
    struct soap *                       soap,
    void **                             service_handle);


void globus_counter__CounterService_s_destroy(
    struct soap *                       soap,
    void *                              service_handle);

These functions can be used as callbacks to set which service a gSOAP handle expects for marshalling of SOAP messages. This is explained further in the sections on Service Data and Notifications.

To enable the service data elements associated with a basic Grid Service, or include those elements associated with a NotificationSource Service, the elements need to be included in the gridService type definition:

gridService struct counter__CounterService_s
{
    _counter__counter *                  counter;
    
    _xsd__QName                         interface;
    _xsd__QName                         serviceDataName;
    _ogsi__LocatorType                  factoryLocator;
    _ogsi__HandleType                   gridServiceHandle;
    _ogsi__ReferenceType                gridServiceReference;
    _ogsi__OperationExtensibilityType   findServiceDataExtensibility;
    _ogsi__OperationExtensibilityType   setServiceDataExtensibility;
    _ogsi__TerminationTimeType          terminationTime;

    _xsd__QName                         notifiableServiceDataName;
    _ogsi__OperationExtensibilityType   subscribeExtensibility;
} counter__CounterService;

All the types in this gridService definition are pointers, allowing many of the service data values for a grid service to be set to NULL, since service data queries in most cases are only concerned with one or a few of the service data elements in the service.

The definitions of the other GridService types are included in the globus_ogsi_types.gsoap definition file.

Port type operations are declared as two separate functions in the gSOAP definition file. This allows the gSOAP compiler to generate stubs that separate the sending and receiving of a SOAP operation on the client:

int counter___add(
    xsd__int                            value,
    void                                dummy);


int counter__addResponse(
    xsd__int                            returnValue,
    void                                dummy);

From these declarations, the following valid C functions are generated:

int soap_send_counter__add(
    struct soap *                       soap,
    const char *                        URL,
    const char *                        soap_action,
    long                                value);

int soap_recv_counter__add(
    struct soap *                       soap,
    struct counter__add *               value);

int soap_send_counter__addResponse(
    struct soap *                       soap,
    const char *                        URL,
    const char *                        soap_action,
    long                                returnValue);

int soap_recv_counter__addResponse(
    struct soap *                       soap,
    struct counter__addResponse *       returnValue);

These functions allow non-blocking requests to be made to the server. The counter_add and counter_addResponse structures are just wrappers to the integer value.

Creating a GPT package for a gSOAP definitions file
2

The easiest way to generate and use the bindings code from your gSOAP definition file, is to include it in a GPT package, using the counter sample package as a template.

The list of files required to generate a GPT package for the counter sample are located in ogsa-c/samples/counter/source:

Makefile.am*
configure.in
bootstrap
globus_ogsa_samples_counter_bindings.gsoap*
globus_ogsa_samples_counter_bindings_namespaces.c*
pkgdata/pkg_data_src.gpt.in*
dirt.sh

The files with (*) need to be modified to work with your service name.

Be sure to replace all instances of globus_ogsa_samples_counter with your own service's name.

In order to build the package, the same prerequisites defined in the CVS portion of the Download section must be met.

From the package directory, the build steps are:

./bootstrap
$GPT_LOCATION/sbin/gpt-build <flavor>

This will run the gSOAP stub/skeleton compiler that will generate the bindings for your service, as well as compile the resulting C source code into a library, and install it into $GLOBUS_LOCATION.

Generate Support Code
3

This section provides details on the support code that must be written for a C client to interact with a Grid Service.

We will use the counter example with notifications enable as example code throughout.

The code can be found at: ogsac/impl/samples/counter/test/notification_counter_test.c.

In the bundles, it's available in BUILD/globus_ogsa_samples_counter_test-0.1/. You can run the sample test by running:

$GLOBUS_LOCATION/test/globus_ogsa_samples_counter_test/test-counter-notify  
      \ http://10.0.0.1:8080/ogsa/services/samples/counter/notification/CounterFactoryService 10

The GSH should be modified for your host and port as appropriate.

This step has the following substeps:

  • Activating Modules
  • Creating a Service Instance
  • Signing the createService Request
  • Sending Operation Requests
  • Signing or Encrypting Operation Requests
  • Querying Service Data
  • Subscribing to Notifications
  a

Activating Modules

   

Since the OGSA-C libraries build on the Globus Toolkit, the Globus model of activating modules is used.

For all OGSA-C clients, the GLOBUS_OGSI_CORE_MODULE should be activated:

result = (globus_result_t) globus_module_activate(
GLOBUS_OGSI_CORE_MODULE);
if(result != GLOBUS_SUCCESS)
{
/* handle error */
}

A module should be activated before any functions from that module are called.

Some frequently used OGSA-C modules are:

  • GLOBUS_OGSA_UTILS_MODULE - provides simple utilities.
  • GLOBUS_OGSI_NOTIFICATION_MODULE - needed for subscribing to notifications
  • GLOBUS_GSOAP_IO_PLUGIN_MODULE - provides globus_io transport for gSOAP
  • GLOBUS_GSOAP_ERROR_PLUGIN_MODULE - provides globus error handling for gSOAP

Modules can be deactivate all at once:

 result = (globus_result_t) globus_module_deactivate_all();

or they can be deactivated in the order they were activated.

  b

Creating a Service Instance

   

As a first step for a Grid Service client, the client should perform the createService operation.

A simple createService operation for the counter service looks like this:

#include <globus_ogsi_core.h>

...

    result = globus_ogsi_core_createService(
        contact, NULL, NULL, NULL, NULL,
        &gsr, &gsh, NULL, NULL, NULL, 0);

Further info can be found in the API documentation.

The contact is the GSH of the factory that creates the service. The GSR and GSH parameters are returned from the createService call.

In this case, the service parameters included in the call are empty. In order to add service parameters that get serialized as the call is sent, a callback should be passed in:

globus_result_t
service_params_callback(
    const char *                        tag,
    void *                              value,
    char **                             buffer,
    size_t *                            buff_length,
    void *                              user_data);

...

    result = globus_ogsi_core_createService(
        contact, NULL, NULL, 
        service_params_callback, service_params_args,
        &gsr, &gsh, NULL, NULL, NULL, 0);

The service_params_callback gets called during serialization of the createService call, and is passed the tag of the service parameters element.

The service_params_args should be user data that can be cast to (void *), and appears in the callback as the value argument.

The result of the callback should be that *buffer is allocated and filled with the serialized XML of the service parameters.

*buff_length should be set to the length of *buffer.

If the user doesn't want to keep track of allocated memory, they should use the soap_malloc function, which will de-allocate all memory associated with that gSOAP handle, when the handle is destroyed.

  c

Signing the createService Request

    If the createService call needs to be signed by the client's proxy certificate, just pass a nonzero value to the last parameter of globus_ogsi_core_createService.
  d

Sending Operation Requests

   

In order to use any of the generated bindings, a gSOAP handle needs to be initialized. We provide a convenience function for this purpose:

#include <globus_ogsa_utils.h>

...

    globus_ogsa_utils_gsoap_handle_init(
        &gsoap_handle,
        COUNTER_DEFAULT_NS,
        1,
        globus_ogsa_samples_counter_bindings_namespaces);

Once the gSOAP handle has been initialized, operation requests can be made:

soap_send_counter__add(gsoap_handle, GSH, NULL, 10);

This sends the add request to the counter service, which adds 10 to its current value. In order to receive the response the service returns from the add operation, the client needs to do the following:

    int                                 new_counter_service_value;


    soap_recv_counter__addResponse(gsoap_handle, &addResponse);

    new_counter_service_value = addResponse.returnValue;
  e

Signing or Encrypting Operation Requests

   

If the counter service in the above example were secure, the request sent from the client would need to be signed or encrypted before being sent.

To do this, the following calls should be made:

    result = globus_ogsa_security_authentication_init(
        security_attrs,
        GSH,
        &gss_context,
        &context_id);
    if(result != GLOBUS_SUCCESS)
    {
        /* do something with error */
    }

    soap_result = soap_register_plugin(
        gsoap_handle,
        &globus_ogsa_security_wsse_gssapi_register);
    if(soap_result != SOAP_OK)
    {
        /* do something with error */
    }

    gssapi_handle = soap_lookup_plugin(
        gsoap_handle,
        GLOBUS_OGSA_SECURITY_WSSE_GSSAPI_PLUGIN_ID);
    if(!gssapi_handle)
    {
        /* error */
    }

    result = globus_ogsa_security_wsse_gssapi_add_gss_context(
        gssapi_handle,
        context_id,
        gss_context);
    if(result != GLOBUS_SUCCESS)
    {
        /* do something with error */
    }

    globus_libc_free(context_id);

This code chunk initializes a GSS context, by negotiating with the grid service via Secure Conversation messages.

Once the context is initialized, it is added to the GSSAPI plugin of the gSOAP handle.

This allows all messages sent with the gSOAP handle to be encrypted or signed with the GSS context.

  f

Querying Service Data

   

Before doing a query of service data, the service type (specifying the service data elements) needs to be specified.

This is done using the callbacks that were generated from the gSOAP service description:

#include <globus_ogsi_service_plugin.h>

...

    globus_ogsi_service_plugin_handle_attr_init(
        &service_attrs,
        globus_counter__CounterService_s_init,
        globus_counter__CounterService_s_destroy,
        globus_counter__CounterService_s_deserialize,
        globus_counter__CounterService_s_serialize);

This initializes a service attributes handle, which can then be passed to the findServiceData function:

#include <globus_ogsi_core.h>
  
...

    globus_list_t *                     service_data_elements;    

...

    result = globus_ogsi_core_findServiceData(
        GSH,
        globus_ogsa_sample_counter_namespaces,
        "CounterUpdate",
        service_attrs,
        NULL,
        0,
        &service_data_elements);
   if(result != GLOBUS_SUCCESS)
   {
       /* do something with error */
   }

The result is an allocated globus_list_t that holds a list of counter__CounterService types. Each element in the list refers to a service data element returned from the findServiceData request.

  g

Subscribing to Notifications

   

In order to receive service data notifications, a notification handle needs to be initialized as a first step:

 #include <globus_ogsi_notification.h>
 #include <globus_ogsi_core.h>

...

    result = globus_ogsi_service_plugin_handle_attr_init(
        &service_attr,
        globus_counter__CounterService_s_init,
        globus_counter__CounterService_s_destroy,
        globus_counter__CounterService_s_deserialize,
        globus_counter__CounterService_s_serialize);
    if(result != GLOBUS_SUCCESS)
    {
        goto exit;
    }

...

    result = globus_ogsi_notification_handle_init(
        &sink_handle,
        globus_ogsa_samples_counter_notify_callback,
        NULL,
        service_attr,
        NULL,
        0,
        "CounterUpdate",
        expireTime,
        globus_ogsa_samples_counter_bindings_namespaces);
    if(result != GLOBUS_SUCCESS)
    {
        /* handle error */
    }

The service attributes are initialized and passed to the notification handle initialization function, as they were with the findServiceData operation discussed previously.

globus_ogsa_samples_counter_notify_callback specifies the callback function that will be called when notifications are received from the service.

Once the notification handle is initialized, a simple notification sink service can be created that listens for incoming notifications:

    result = globus_ogsi_notification_sink_register_serve(
        sink_handle);
    if(result != GLOBUS_SUCCESS)
    {
        /* handle error */
    }

Now you're ready to perform the subscription and start receiving notifications:

    result = globus_ogsi_notification_subscribe(
        sink_handle,
        NULL,
        GSH);
    if(result != GLOBUS_SUCCESS)
    {
        /* handle error */
    }

Once this call returns, notifications will be sent to listening notification sink, and the specified user callback will be called. The prototype definition for the callback is:

void
globus_ogsa_samples_counter_notify_callback(
    globus_ogsi_notification_handle_t   handle,
    globus_list_t *                     elements,
    void *                              data);

In the callback, the elements parameter contains a list of counter__CounterService types, each referring to a service data element being notified on.

There are matching unsubscribe and unregister functions in the notification api. See the API documentation for further details.

GRAM Client Wrapper API

The OGSA-C bundle includes a globus_gram_client API that works with GT3.2 Managed Job Services. This is useful for anyone who has applications built on the GT2 globus_gram_client API.

For this implementation of the API, the function prototypes are identical, but the underlying implementation interacts with a GT3.2 Managed Job Services, instead of GT2 jobmanagers.

This implementation of the globus_gram_client API can be linked into legacy GT2 applications to enable the application to work with GT3.2 Managed Job services.

Users wishing to do this should take notice of the following:

  • The GPT package name for the globus_gram_client package has changed in this implementation to globus_gt3_gram_client, and when linking the library into an application, -lglobus_gt3_gram_client should be used.
  • The header files for the package remain the same.
  • The globus_gram_client_ping behavior has changed. It currently verifies that the factory is available for creating jobs, it doesn't verify that authentication takes place successfully.

The following job signals:

  • GLOBUS_GRAM_PROTOCOL_JOB_SIGNAL_SUSPEND
  • GLOBUS_GRAM_PROTOCOL_JOB_SIGNAL_RESUME
  • GLOBUS_GRAM_PROTOCOL_JOB_SIGNAL_PRIORITY
  • GLOBUS_GRAM_PROTOCOL_JOB_SIGNAL_STOP_MANAGER

are currently unsupported.

The resource_manager_contact passed to globus_gram_client_register_job_requestshould be the GSH of the ManagedJobFactoryService, instead of the GT2 contact string.