API Plugins

October 13, 2016

Univeristy of Birmingham

Birmingham, England

Jason M. Coposky

@jason_coposky

Executive Director, iRODS Consortium

The iRODS Plugin Architecture

iRODS 4.2 provides 7 plugin interfaces

  • microservices
  • resources
  • authentication
  • network
  • database
  • RPC API
  • rule engine

Anatomy of a Plugin

  • Built as a dynamic shared object
  • provides a plugin_factory method which instantiates the plugin object
  • contains a class derived from a plugin interface base class:
    • api_entry
    • auth
    • network
    • resource
    • database
    • ms_table_entry
    • rule_engine_plugin<>

Building and Installing the Example Code

sudo apt-get -y install irods-externals-*

 

export PATH=/opt/irods-externals/cmake3.5.2-0/bin:/opt/irods-externals/clang3.8-0/bin:$PATH

 

 which clang++
/opt/irods-externals/clang3.8-0/bin/clang++

 which cmake
/opt/irods-externals/cmake3.5.2-0/bin/cmake

 

sudo apt-get install irods-dev

mkdir ~/build_api

cd ~/build_api

cmake ~/irods_training/advanced/irods_api_example

make package

sudo dpkg -i irods-api-plugin-example_4.2.0~trusty_amd64.deb

Follow along in the irods_training repository

The code can be found at:

~/irods_training/advanced/irods_api_example/src/libapi-example.cpp

Start with the Factory

extern "C"
irods::api_entry* plugin_factory(
    const std::string&,     //_inst_name
    const std::string& ) {  // _context

    ....

    return api;
}
  • Must have C linkage
  • Must return an irods::plugin_base derived type
  • Must be named "plugin_factory"
  • Requires two const std::string& to match irods::plugin_base signature but not necessary

Define an api_def_t object

irods::apidef_t def = { 1310,             // api number
                        RODS_API_VERSION, // api version
                        NO_USER_AUTH,     // client auth
                        NO_USER_AUTH,     // proxy auth
                        "HelloInp_PI", 0, // in PI / bs flag
                        "HelloOut_PI", 0, // out PI / bs flag
                        std::function<
                           int( rsComm_t*,helloInp_t*,helloOut_t**)>(
                                rs_hello_world),  // operation
                                "rs_hello_world", // operation name
                                0,                // null clear fcn
                                (funcPtr)CALL_HELLOINP_HELLO_OUT
                       };
  • Utilize a proxy object to initialize the api_entry
    • Includes API number, auth, packing instructions, byte stream flags
  • Also takes the wired std::function object

Instantiate the api_entry and wire the serialization fcns

irods::api_entry* api = new irods::api_entry( def );

#ifdef RODS_SERVER
    irods::re_serialization::add_operation(
        typeid(helloInp_t*),
        serialize_helloInp_ptr );

    irods::re_serialization::add_operation(
        typeid(helloOut_t**),
        serialize_helloOut_ptr_ptr );
#endif // RODS_SERVER
  • instantiate an irods::api_entry* passing the api_def_t
  • If this is a server build, call the add_operation to the serialization table
    • pass the type_id for the parameter type
    • pass the function pointer which performs parameter serialization

Assign packing instructions to the api_entry

    ...

    api->in_pack_key   = "HelloInp_PI";
    api->in_pack_value = HelloInp_PI;

    api->out_pack_key   = "HelloOut_PI";
    api->out_pack_value = HelloOut_PI;

    api->extra_pack_struct[ "OtherOut_PI" ] = OtherOut_PI;

    return api;

}

Assign packing instruction key-value pairs for input and output types

Define the input and output types and packInst

typedef struct {
   int  _this;
   char _that [64];
} helloInp_t;

typedef struct {
    double _value;
} otherOut_t;

 typedef struct {
    int  _this;
    char _that [64];
    otherOut_t _other;
} helloOut_t;

#define HelloInp_PI "int _this; str _that[64];"
#define OtherOut_PI "double _value;"
#define HelloOut_PI "int _this; str _that[64]; struct OtherOut_PI;"

Define a call handler for this combination of types

int call_helloInp_helloOut(
    irods::api_entry* _api,
    rsComm_t*         _comm,
    helloInp_t*       _inp,
    helloOut_t**      _out ) {
    return _api->call_handler<
               rsComm_t*,
               helloInp_t*,
               helloOut_t** >(
                   _comm,
                   _inp,
                   _out );
}

Each combination of parameter types has a unique call handler to deal with the void* type erasure of the packing instructions - this is used in the std::function of the api_def_t

The call handler - Client vs Server Building

#ifdef RODS_SERVER
    #define CALL_HELLOINP_HELLO_OUT call_helloInp_helloOut
#else
    #define CALL_HELLOINP_HELLO_OUT NULL
#endif

Based on macros, the same plugin source file generates both

  • client
  • server

 

Several parts are undefined for the client build

The actual API definition

int rs_hello_world( rsComm_t*, helloInp_t* _inp, helloOut_t** _out ) {
    rodsLog( LOG_NOTICE, "Dynamic API - HELLO WORLD" );

    ( *_out ) = ( helloOut_t* )malloc( sizeof( helloOut_t ) );
    ( *_out )->_this = 42;
    strncpy( ( *_out )->_that, "hello, world.", 63 );
    ( *_out )->_other._value = 128.0;

    rodsLog( LOG_NOTICE, "Dynamic API - this [%d] that [%s]", _inp->_this, _inp->_that );
    rodsLog( LOG_NOTICE, "Dynamic API - DONE" );
    return 0;
}
  • Log the input parameters
  • Output parameters must be allocated first
  • Packing Instructions can include other PIs which the _other tests

Questions?

Follow along in the irods_training repository

The code can be found at:

~/irods_training/advanced/irods_api_example/src/iapi_example.cpp

Client Main - get the iRODS environment and connect

int
main( int, char** ) {

    signal( SIGPIPE, SIG_IGN );

    rodsEnv myEnv;
    int status = getRodsEnv( &myEnv );
    if ( status < 0 ) {
        rodsLogError( LOG_ERROR, status, "main: getRodsEnv error. " );
        exit( 1 );
    }

    rErrMsg_t errMsg;
    rcComm_t *conn;
    conn = rcConnect(
               myEnv.rodsHost,
               myEnv.rodsPort,
               myEnv.rodsUserName,
               myEnv.rodsZone,
               0, &errMsg );

    if ( conn == NULL ) {
        exit( 2 );
    }

Initialize the API table

    irods::pack_entry_table& pk_tbl = irods::get_pack_table();
    irods::api_entry_table& api_tbl = irods::get_client_api_table();
    init_api_table( api_tbl, pk_tbl );

Initialize the API table with all of the client-side plugins

Login to iRODS as a client user

    if ( strcmp( myEnv.rodsUserName, PUBLIC_USER_NAME ) != 0 ) {
        status = clientLogin( conn );
        if ( status != 0 ) {
            rcDisconnect( conn );
            exit( 7 );
        }
    }
  • clientLogin extracts the iRODS environment from the file system

Instantiate the input parameter and set values

    helloInp_t inp;
    memset( &inp, 0, sizeof( inp ) );
    inp._this = 42;
    strncpy( inp._that, "hello, world.", 64 );

Make the Client RPC API Request by number

    void *tmp_out = NULL;
    status = procApiRequest( conn, 1310, &inp, NULL, &tmp_out, NULL );
  • API request is done by API number
  • Output structure starts out as a null void*
  • No input or output byte streams are requested

Error handling, reporting, and disconnect

    if ( status < 0 ) {
        printf( "\n\nERROR - failed to call our api\n\n\n" );
        return 0;
    }
    else {
        helloOut_t* out = static_cast<helloOut_t*>( tmp_out );
        if ( out != NULL ) {
            printf( "\n\nthis [%d]  that [%s] other [%f]\n",
                    out->_this, out->_that, out->_other._value );
        }
        else {
            printf( "ERROR: the 'out' variable is null\n" );
        }
    }

    rcDisconnect( conn );
}

Testing Output

$ iapi_example

this [42]  that [hello, world.] other [128.000000]

Questions?

Birmingham - API Plugins

By jason coposky

Birmingham - API Plugins

  • 1,474