Globus Toolkit 3.0 - Last Updated 06/27/2003
Introduction
Download
Implementing a Client
Gram Client Wrapper API
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.
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:
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. Building the source from CVS can be done using the ogsa-c/build_tools/cvs-build-ogsa script. Usually you can just run:
./cvs-build-ogsa gcc32dbgThe -help option gives further information about using that script. There a few pre-requisites before running the script:
The following steps are involved in writing a C client:
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:
#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 distro for further documentation.
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.
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.
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.shThe 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.
$GLOBUS_LOCATION/test/globus_ogsa_samples_counter_test/test-counter-notify \The GSH should be modified for your host and port as appropriate.
http://10.0.0.1:8080/ogsa/services/samples/counter/notification/CounterFactoryService 10
This section has the following subsections:
Activating Modules
Creating a Service Instance
Signing the createService Request
Sending Operation Requests
Signing or Encrypting Operation Requests
Querying Service Data
Subscribing to Notifications
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:
result = (globus_result_t) globus_module_deactivate_all();or they can be deactivated in the order they were activated.
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 deallocate all memory associated with that gSOAP handle, when the handle is destroyed.
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.
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 received the response the service returns from the add operation, the client needs to do the followiing:
int new_counter_service_value; soap_recv_counter__addResponse(gsoap_handle, &addResponse); new_counter_service_value = addResponse.returnValue;
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.
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.
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 unsbuscribe and unregister functions in the notification api. See the API documentation for further details.