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