API Plugins

June 7-9, 2016
iRODS User Group Meeting 2016
Chapel Hill, NC
Jason M. Coposky
@jason_coposky
Interim Executive Director







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?
UGM 2016 - API Plugins
By iRODS Consortium
UGM 2016 - API Plugins
- 1,817