From d541c49c6f23d5a7b8808d62a33d40b689f28bcd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 7 Apr 2018 18:20:33 +0200 Subject: [PATCH] Plugin RPC callback interface have been unified between backend, netconf and restconf. * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. --- CHANGELOG.md | 7 +- apps/backend/backend_client.c | 4 +- apps/backend/backend_main.c | 2 +- apps/backend/clixon_backend_handle.c | 107 -------------------- apps/backend/clixon_backend_handle.h | 40 ++------ apps/backend/clixon_backend_transaction.h | 10 +- apps/netconf/clixon_netconf.h | 15 --- apps/netconf/netconf_lib.c | 50 +++++----- apps/netconf/netconf_main.c | 3 +- apps/netconf/netconf_rpc.c | 32 ++++-- apps/restconf/restconf_main.c | 4 +- apps/restconf/restconf_methods.c | 26 ++++- example/example.yang | 16 +++ example/example_backend.c | 40 ++++---- example/example_netconf.c | 16 +++ example/example_restconf.c | 18 ++++ lib/clixon/clixon_plugin.h | 15 +++ lib/src/clixon_plugin.c | 115 ++++++++++++++++++++++ test/test_netconf.sh | 4 +- test/test_restconf.sh | 28 +++++- 20 files changed, 323 insertions(+), 229 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9ab88eb..fd1171f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,18 @@ ## 3.6.0 (Upcoming) - ### Major changes: * Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. + * Plugin RPC callback interface have been unified between backend, netconf and restconf. + * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` + * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. * Moved specific plugin functions from apps/ to generic functions in lib/ * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() * Removed some obscure plugin code that seem not to be used (please report if needed!) - * Client-local netconf plugins netconf_plugin_callbacks() * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() @@ -20,6 +22,7 @@ * Example of migrating a backend plugin module: * Add all callbacks in a clixon_plugin_api struct * Rename plugin_init() -> clixon_plugin_init() and return api as function value + * Rename backend_rpc_cb_register() -> rpc_callback_register() for any RPC/restconf operation POST calls ``` /* This is old style with hardcoded function names (eg plugin_start) */ int plugin_start(clicon_handle h, int argc, char **argv) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index cf0bd2a5..97b583da 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -909,10 +909,10 @@ from_client_msg(clicon_handle h, } else{ clicon_err_reset(); - if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0){ + if ((ret = rpc_callback_call(h, xe, cbret, ce)) < 0){ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; - clicon_log(LOG_NOTICE, "%s Error in backend_rpc_call:%s", __FUNCTION__, xml_name(xe)); + clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); goto reply; /* Dont quit here on user callbacks */ } if (ret == 0){ /* not handled by callback */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 45c097c9..ed2e9e64 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -88,7 +88,7 @@ backend_terminate(clicon_handle h) yspec_free(yspec); clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ - backend_rpc_cb_delete_all(); + rpc_callback_delete_all(); if (pidfile) unlink(pidfile); if (sockpath) diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index aeba8a23..de6563dc 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -430,110 +430,3 @@ subscription_each(clicon_handle h, return hs; } -/*-------------------------------------------------------------------- - * Backend netconf rpc callbacks - */ -typedef struct { - qelem_t rc_qelem; /* List header */ - backend_rpc_cb rc_callback; /* RPC Callback */ - void *rc_arg; /* Application specific argument to cb */ - char *rc_tag; /* Xml tag when matched, callback called */ -} backend_rpc_cb_entry; - -/* List of backend rpc callback entries */ -static backend_rpc_cb_entry *rpc_cb_list = NULL; - -/*! Register netconf backend rpc callback - * Called from plugin to register a callback for a specific netconf XML tag. - * - * @param[in] h clicon handle - * @param[in] cb, Callback called - * @param[in] arg, Arg to send to callback - * @param[in] tag Xml tag when callback is made - * @see backend_rpc_cb_call - */ -int -backend_rpc_cb_register(clicon_handle h, - backend_rpc_cb cb, - void *arg, - char *tag) -{ - backend_rpc_cb_entry *rc; - - if ((rc = malloc(sizeof(backend_rpc_cb_entry))) == NULL) { - clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); - goto catch; - } - memset (rc, 0, sizeof (*rc)); - rc->rc_callback = cb; - rc->rc_arg = arg; - rc->rc_tag = strdup(tag); /* XXX strdup memleak */ - INSQ(rc, rpc_cb_list); - return 0; -catch: - if (rc){ - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } - return -1; -} - -/*! Search netconf backend callbacks and invoke if match - * This is internal system call, plugin is invoked (does not call) this functino - * @param[in] h clicon handle - * @param[in] xe Sub-tree (under xorig) at child of rpc: . - * @param[in] ce Client (session) entry - * @param[out] cbret Return XML, error or OK as cbuf - * - * @retval -1 Error - * @retval 0 OK, not found handler. - * @retval 1 OK, handler called - * @see backend_rpc_cb_register - */ -int -backend_rpc_cb_call(clicon_handle h, - cxobj *xe, - struct client_entry *ce, - cbuf *cbret) -{ - backend_rpc_cb_entry *rc; - int retval = -1; - - if (rpc_cb_list == NULL) - return 0; - rc = rpc_cb_list; - do { - if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ - if ((retval = rc->rc_callback(h, xe, ce, cbret, rc->rc_arg)) < 0){ - clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); - goto done; - } - else{ - retval = 1; /* handled */ - goto done; - } - } - rc = NEXTQ(backend_rpc_cb_entry *, rc); - } while (rc != rpc_cb_list); - retval = 0; - done: - return retval; -} - -/*! Delete all state data callbacks. - */ -int -backend_rpc_cb_delete_all(void) -{ - backend_rpc_cb_entry *rc; - - while((rc = rpc_cb_list) != NULL) { - DELQ(rc, rpc_cb_list, backend_rpc_cb_entry *); - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } - return 0; -} - diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index fa596ed0..3855a14b 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -43,30 +43,6 @@ /* * Types */ -struct client_entry; -typedef int (*backend_rpc_cb)( - clicon_handle h, /* CLicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret,/* Reply eg ... */ - void *arg /* Argument given at register */ -); -typedef backend_rpc_cb backend_netconf_cb_t; /* XXX backward compat */ - - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - -/* - * Log for netconf notify function (config_client.c) - */ -int backend_notify(clicon_handle h, char *stream, int level, char *txt); -int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); - /* subscription callback */ typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg); @@ -82,6 +58,14 @@ struct handle_subscription{ void *hs_arg; /* Callback argument */ }; +/* + * Prototypes + */ +/* Log for netconf notify function (config_client.c) */ +int backend_notify(clicon_handle h, char *stream, int level, char *txt); +int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); + + struct handle_subscription *subscription_add(clicon_handle h, char *stream, enum format_enum format, char *filter, subscription_fn_t fn, void *arg); @@ -92,12 +76,4 @@ int subscription_delete(clicon_handle h, char *stream, struct handle_subscription *subscription_each(clicon_handle h, struct handle_subscription *hprev); -int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg, - char *tag); - -int backend_rpc_cb_call(clicon_handle h, cxobj *xe, struct client_entry *ce, - cbuf *cbret); - -int backend_rpc_cb_delete_all(void); - #endif /* _CLIXON_BACKEND_HANDLE_H_ */ diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index 1d2a523e..a290c5f1 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -41,16 +41,8 @@ #define _CLIXON_BACKEND_TRANSACTION_H_ /* - * Types + * Prototypes */ - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - /* Transaction callback data accessors for client plugins * (defined in config_dbdep.c) * @see transaction_data_t internal structure diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index e3b1680e..e2fbb5bb 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -39,27 +39,12 @@ #ifndef _CLIXON_NETCONF_H_ #define _CLIXON_NETCONF_H_ -/* - * Types - */ -typedef int (*netconf_cb_t)( - clicon_handle h, - cxobj *xn, /* Request: */ - cxobj **xret, /* Return xml tree, eg ... */ - void *arg /* Argument given at netconf_register_callback() */ - ); - /* * Prototypes * (Duplicated. Also in netconf_*.h) */ int netconf_output(int s, cbuf *xf, char *msg); -int netconf_register_callback(clicon_handle h, - netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag); /* Xml tag when callback is made */ - int netconf_xpath(cxobj *xsearch, cxobj *xfilter, cbuf *xf, cbuf *xf_err, diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index debd0c3d..5e5e21d2 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -71,45 +71,48 @@ enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */ int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */ +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. + * @param[in] cb Netconf packet (cligen buffer) + */ int -add_preamble(cbuf *xf) +add_preamble(cbuf *cb) { if (transport == NETCONF_SOAP) - cprintf(xf, "\n\n" + cprintf(cb, "\n\n" ""); return 0; } -/* - * add_postamble - * add netconf xml postamble of message. That is, xml after the body of the message. +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. * for soap this is the envelope stuff, for ssh this is ]]>]]> + * @param[in] cb Netconf packet (cligen buffer) */ int -add_postamble(cbuf *xf) +add_postamble(cbuf *cb) { switch (transport){ case NETCONF_SSH: - cprintf(xf, "]]>]]>"); /* Add RFC4742 end-of-message marker */ + cprintf(cb, "]]>]]>"); /* Add RFC4742 end-of-message marker */ break; case NETCONF_SOAP: - cprintf(xf, "\n" ""); + cprintf(cb, "\n" ""); break; } return 0; } -/* - * add_error_preamble +/*! Add error_preamble * compared to regular messages (see add_preamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_preamble(cbuf *xf, char *reason) +add_error_preamble(cbuf *cb, + char *reason) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" + cprintf(cb, "" "" "" "" @@ -121,26 +124,26 @@ add_error_preamble(cbuf *xf, char *reason) "", reason); break; default: - if (add_preamble(xf) < 0) + if (add_preamble(cb) < 0) return -1; break; } return 0; } -/* - * add_error_postamble +/*! Add error postamble * compared to regular messages (see add_postamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_postamble(cbuf *xf) +add_error_postamble(cbuf *cb) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" ""); + cprintf(cb, "" ""); default: /* fall through */ - if (add_postamble(xf) < 0) + if (add_postamble(cb) < 0) return -1; break; } @@ -150,7 +153,9 @@ add_error_postamble(cbuf *xf) /*! Get "target" attribute, return actual database given candidate or running * Caller must do error handling - * @retval dbname Actual database file name + * @param[in] xn XML tree + * @param[in] path + * @retval dbname Actual database file name */ char * netconf_get_target(cxobj *xn, @@ -180,11 +185,11 @@ netconf_get_target(cxobj *xn, */ int netconf_output(int s, - cbuf *xf, + cbuf *cb, char *msg) { - char *buf = cbuf_get(xf); - int len = cbuf_len(xf); + char *buf = cbuf_get(cb); + int len = cbuf_len(cb); int retval = -1; clicon_debug(1, "SEND %s", msg); @@ -207,3 +212,4 @@ netconf_output(int s, done: return retval; } + diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index f219792e..ad900535 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -262,6 +262,8 @@ netconf_terminate(clicon_handle h) { yang_spec *yspec; + clixon_plugin_exit(h); + rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -402,7 +404,6 @@ main(int argc, if (event_loop() < 0) goto done; done: - clixon_plugin_exit(h); netconf_terminate(h); clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 8cc23e1b..5c8bc143 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -849,8 +849,10 @@ netconf_create_subscription(clicon_handle h, return retval; } -/*! See if there is any callback registered for this tag +/*! See if there is any application defined RPC for this tag * + * This may either be local client-side or backend. If backend send as netconf + * RPC. * @param[in] h clicon handle * @param[in] xn Sub-tree (under xorig) at child of rpc: . * @param[out] xret Return XML, error or OK @@ -871,6 +873,8 @@ netconf_application_rpc(clicon_handle h, yang_stmt *youtput; cxobj *xoutput; cbuf *cb = NULL; + cbuf *cbret = NULL; + int ret; /* First check system / netconf RPC:s */ if ((cb = cbuf_new()) == NULL){ @@ -891,9 +895,9 @@ netconf_application_rpc(clicon_handle h, /* Find yang rpc statement, return yang rpc statement if found */ if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) goto done; - /* Check if found */ if (yrpc != NULL){ + /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -904,13 +908,20 @@ netconf_application_rpc(clicon_handle h, if (xml_yang_validate_add(xn, NULL) < 0) goto done; } - /* - * 1. Check xn arguments with input statement. - * 2. Send to backend as clicon_msg-encode() - * 3. In backend to similar but there call actual backend - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; + } + /* Look for local (client-side) netconf plugins. */ + if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + goto done; + } + else /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; /* Sanity check of outgoing XML */ if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); @@ -930,9 +941,10 @@ netconf_application_rpc(clicon_handle h, done: if (cb) cbuf_free(cb); + if (cbret) + cbuf_free(cbret); return retval; } - /*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions. @@ -1017,6 +1029,8 @@ netconf_rpc_dispatch(clicon_handle h, } /* Others */ else { + /* Look for application-defined RPC. This may either be local + client-side or backend. If backend send as netconf RPC. */ if ((retval = netconf_application_rpc(h, xe, xret)) < 0) goto done; if (retval == 0){ /* not handled by callback */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index d7912446..89d52114 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -432,7 +432,8 @@ restconf_terminate(clicon_handle h) { yang_spec *yspec; - clicon_debug(0, "%s", __FUNCTION__); + clixon_plugin_exit(h); + rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -605,7 +606,6 @@ main(int argc, } retval = 0; done: - clixon_plugin_exit(h); restconf_terminate(h); return retval; } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 547aa3ec..e28bcb89 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1064,6 +1064,7 @@ api_operations_post(clicon_handle h, cxobj *xret = NULL; cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ + cxobj *xe; cxobj *xbot = NULL; yang_node *y = NULL; cxobj *xinput; @@ -1071,6 +1072,8 @@ api_operations_post(clicon_handle h, cxobj *x; cxobj *xa; char *username; + cbuf *cbret = NULL; + int ret = 0; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1109,7 +1112,7 @@ api_operations_post(clicon_handle h, /* XXX: something strange for rpc user */ if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; -#if 1 +#if 0 { cbuf *c = cbuf_new(); clicon_xml2cbuf(c, xtop, 0, 0); @@ -1150,9 +1153,24 @@ api_operations_post(clicon_handle h, } } } - /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; + } + xe = NULL; + while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { + /* Look for local (client-side) restconf plugins. */ + if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) + goto done; + } + break; /* Just one if local */ + } + if (ret == 0) /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; if ((cbx = cbuf_new()) == NULL) goto done; xoutput=xpath_first(xret, "/"); @@ -1198,5 +1216,7 @@ api_operations_post(clicon_handle h, xml_free(xret); if (cbx) cbuf_free(cbx); + if (cbret) + cbuf_free(cbret); return retval; } diff --git a/example/example.yang b/example/example.yang index eb4dbfdd..42d8236f 100644 --- a/example/example.yang +++ b/example/example.yang @@ -25,4 +25,20 @@ module example { type string; } } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } diff --git a/example/example_backend.c b/example/example_backend.c index 1c4bba28..da16ab0b 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -125,9 +125,9 @@ notification_timer_setup(clicon_handle h) static int fib_route(clicon_handle h, /* Clicon handle */ cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, "" "ipv4" @@ -139,10 +139,10 @@ fib_route(clicon_handle h, /* Clicon handle */ /*! Smallest possible RPC declaration for test */ static int empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; @@ -152,9 +152,9 @@ empty(clicon_handle h, /* Clicon handle */ static int route_count(clicon_handle h, cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; @@ -278,20 +278,20 @@ clixon_plugin_init(clicon_handle h) if (notification_timer_setup(h) < 0) goto done; /* Register callback for routing rpc calls */ - if (backend_rpc_cb_register(h, fib_route, - NULL, - "fib-route"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, fib_route, + NULL, + "fib-route"/* Xml tag when callback is made */ + ) < 0) goto done; - if (backend_rpc_cb_register(h, route_count, - NULL, - "route-count"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, route_count, + NULL, + "route-count"/* Xml tag when callback is made */ + ) < 0) goto done; - if (backend_rpc_cb_register(h, empty, - NULL, - "empty"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, empty, + NULL, + "empty"/* Xml tag when callback is made */ + ) < 0) goto done; return &api; done: diff --git a/example/example_netconf.c b/example/example_netconf.c index f574de6f..dc8f2543 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -62,6 +62,19 @@ plugin_exit(clicon_handle h) return 0; } +/*! Local example netconf rpc callback + */ +int netconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + clicon_debug(1, "%s restconf", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static struct clixon_plugin_api api = { @@ -81,6 +94,9 @@ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, netconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; return &api; } diff --git a/example/example_restconf.c b/example/example_restconf.c index 380326ec..d65f9b19 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -266,6 +266,21 @@ plugin_credentials(clicon_handle h, goto done; } +/*! Local example restconf rpc callback + */ +int restconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + // FCGX_Request *r = (FCGX_Request *)arg; + clicon_debug(1, "%s", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { @@ -285,5 +300,8 @@ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, restconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; return &api; } diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 82a44adc..2130134f 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -44,6 +44,15 @@ /* The dynamicically loadable plugin object handle */ typedef void *plghndl_t; +/* Registered RPC callback function */ +typedef int (*clicon_rpc_cb)( + clicon_handle h, /* Clicon handle */ + cxobj *xn, /* Request: */ + cbuf *cbret, /* Return xml tree, eg ..., rc_callback = cb; + rc->rc_arg = arg; + rc->rc_tag = strdup(tag); /* XXX strdup memleak */ + INSQ(rc, rpc_cb_list); + return 0; + done: + if (rc){ + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return -1; +} + +/*! Delete all RPC callbacks + */ +int +rpc_callback_delete_all(void) +{ + rpc_callback_t *rc; + + while((rc = rpc_cb_list) != NULL) { + DELQ(rc, rpc_cb_list, rpc_callback_t *); + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return 0; +} + +/*! Search RPC callbacks and invoke if XML match with tag + * + * @param[in] h clicon handle + * @param[in] xn Sub-tree (under xorig) at child of rpc: . + * @param[out] xret Return XML, error or OK + * @param[in] arg Domain-speific arg (eg client_entry) + * + * @retval -1 Error + * @retval 0 OK, not found handler. + * @retval 1 OK, handler called + */ +int +rpc_callback_call(clicon_handle h, + cxobj *xe, + cbuf *cbret, + void *arg) +{ + rpc_callback_t *rc; + int retval = -1; + + if (rpc_cb_list == NULL) + return 0; + rc = rpc_cb_list; + do { + if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ + if ((retval = rc->rc_callback(h, xe, cbret, arg, rc->rc_arg)) < 0){ + clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); + goto done; + } + else{ + retval = 1; /* handled */ + goto done; + } + } + rc = NEXTQ(rpc_callback_t *, rc); + } while (rc != rpc_cb_list); + retval = 0; + done: + return retval; +} diff --git a/test/test_netconf.sh b/test/test_netconf.sh index f94b7226..9877c770 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -25,7 +25,6 @@ cat < $cfg EOF -echo "clixon_backend -zf $cfg" # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg @@ -147,6 +146,9 @@ expecteof "$clixon_netconf -qf $cfg" " new "netconf rpc w/o namespace" expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +new "netconf client-side rpc" +expecteof "$clixon_netconf -qf $cfg" "example]]>]]>" "^ok]]>]]>$" + new "netconf subscription" expectwait "$clixon_netconf -qf $cfg" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 13586009..3dba5353 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -50,6 +50,19 @@ module example{ output { } } + rpc client-rpc { + description "Example local client-side rpc"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } EOF @@ -72,7 +85,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D sleep 1 @@ -93,12 +106,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect="" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -237,6 +250,15 @@ match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi + +new "restconf local client rpc using POST xml" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) +expect="ok" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + # XXX cant get -H to work #expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5'