Merge branch dispatcher and broke out pagination callbacks to use it
* Merge branch 'dcornejo-master' * Broke out pagination callback API from state data callbacks * New pagination callback API uses new dispatcher from netgate, thanks @dcornejo * Register callback with: `clixon_pagination_cb_register()` * Use accessor functions `pagination_offset()`, `pagination_limit()`, etc * Reverted state data callback API to pre-5.3 (see C/CLI API changes below)
This commit is contained in:
commit
ce06f25be7
19 changed files with 996 additions and 123 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -73,3 +73,4 @@ test/vagrant/site.mk
|
|||
test/cicd/site.mk
|
||||
doc/html
|
||||
|
||||
.idea/
|
||||
|
|
|
|||
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -34,6 +34,16 @@
|
|||
## 5.4.0
|
||||
Expected: November, 2021
|
||||
|
||||
Thanks netgate for providing the dispatcher code!
|
||||
|
||||
### New features
|
||||
|
||||
* Broke out pagination callback API from state data callbacks
|
||||
* New pagination callback API uses new dispatcher from netgate, thanks @dcornejo
|
||||
* Register callback with: `clixon_pagination_cb_register()`
|
||||
* Use accessor functions `pagination_offset()`, `pagination_limit()`, etc
|
||||
* Reverted state data callback API to pre-5.3 (see C/CLI API changes below)
|
||||
|
||||
### API changes on existing protocol/config features
|
||||
|
||||
Users may have to change how they access the system
|
||||
|
|
@ -41,8 +51,24 @@ Users may have to change how they access the system
|
|||
* NETCONF hello errors, such as wrong session-id, prefix, namespace terminates session
|
||||
* Instead of returning an rpc-error reply
|
||||
|
||||
### C/CLI-API changes on existing features
|
||||
|
||||
Developers may need to change their code
|
||||
|
||||
* Statedata plugin callbacks are reverted to pre-5.3:
|
||||
* This has been done as a consequence of breaking out the pagination state API as a separate API.
|
||||
* The reverted state data callback signature is as follows:
|
||||
```
|
||||
int statedata(clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
cxobj *xstate)
|
||||
```
|
||||
|
||||
### Minor features
|
||||
|
||||
* Added set/get pointer API to clixon_data:
|
||||
* clicon_ptr_get(), clicon_ptr_set(),
|
||||
* Restconf YANG PATCH according to RFC 8072
|
||||
* Changed YANG PATCH enabling:
|
||||
* Now: `./configure --enable-yang-patch`
|
||||
|
|
@ -109,6 +135,7 @@ Users may have to change how they access the system
|
|||
Developers may need to change their code
|
||||
|
||||
* You need to change all statedata plugin callback for the new pagination feature
|
||||
* NOTE THIS CHANGE IS REVERTED IN 5.4
|
||||
* If you dont use pagination you can ignore the values of the new parameters
|
||||
* The updated callback signature is as follows:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ get_client_statedata(clicon_handle h,
|
|||
/* Add default global state */
|
||||
if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0)
|
||||
goto done;
|
||||
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
|
||||
if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
|
||||
|
|
@ -258,7 +257,7 @@ get_client_statedata(clicon_handle h,
|
|||
goto fail;
|
||||
}
|
||||
/* Use plugin state callbacks */
|
||||
if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, PAGINATION_NONE, 0, 0, NULL, xret)) < 0)
|
||||
if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -490,7 +489,7 @@ get_list_pagination(clicon_handle h,
|
|||
}
|
||||
if (yang_keyword_get(ylist) != Y_LIST &&
|
||||
yang_keyword_get(ylist) != Y_LEAF_LIST){
|
||||
if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0)
|
||||
if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not list or leaf-list") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
|
@ -539,35 +538,37 @@ get_list_pagination(clicon_handle h,
|
|||
/* where */
|
||||
if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL)
|
||||
where = xml_body(x);
|
||||
/* Build a "predicate" cbuf
|
||||
* This solution uses xpath predicates to translate "limit" and "offset" to
|
||||
* relational operators <>.
|
||||
*/
|
||||
if ((cbpath = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
/* This uses xpath. Maybe limit should use parameters */
|
||||
if (xpath)
|
||||
cprintf(cbpath, "%s", xpath);
|
||||
else
|
||||
cprintf(cbpath, "/");
|
||||
if (where)
|
||||
cprintf(cbpath, "[%s]", where);
|
||||
if (offset){
|
||||
cprintf(cbpath, "[%u <= position()", offset);
|
||||
if (limit)
|
||||
cprintf(cbpath, " and position() < %u", limit+offset);
|
||||
cprintf(cbpath, "]");
|
||||
}
|
||||
else if (limit)
|
||||
cprintf(cbpath, "[position() < %u]", limit);
|
||||
/* Append predicate to original xpath and replace it */
|
||||
xpath2 = cbuf_get(cbpath);
|
||||
|
||||
/* Read config */
|
||||
switch (content){
|
||||
case CONTENT_CONFIG: /* config data only */
|
||||
case CONTENT_ALL: /* both config and state */
|
||||
/* Build a "predicate" cbuf
|
||||
* This solution uses xpath predicates to translate "limit" and "offset" to
|
||||
* relational operators <>.
|
||||
*/
|
||||
if ((cbpath = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
/* This uses xpath. Maybe limit should use parameters */
|
||||
if (xpath)
|
||||
cprintf(cbpath, "%s", xpath);
|
||||
else
|
||||
cprintf(cbpath, "/");
|
||||
if (where)
|
||||
cprintf(cbpath, "[%s]", where);
|
||||
if (offset){
|
||||
cprintf(cbpath, "[%u <= position()", offset);
|
||||
if (limit)
|
||||
cprintf(cbpath, " and position() < %u", limit+offset);
|
||||
cprintf(cbpath, "]");
|
||||
}
|
||||
else if (limit)
|
||||
cprintf(cbpath, "[position() < %u]", limit);
|
||||
|
||||
/* Append predicate to original xpath and replace it */
|
||||
xpath2 = cbuf_get(cbpath);
|
||||
/* specific xpath */
|
||||
if (xmldb_get0(h, db, YB_MODULE, nsc, xpath2?xpath2:"/", 1, &xret, NULL, NULL) < 0) {
|
||||
if ((cbmsg = cbuf_new()) == NULL){
|
||||
|
|
@ -603,11 +604,12 @@ get_list_pagination(clicon_handle h,
|
|||
pagmode = PAGINATION_LOCK;
|
||||
else
|
||||
pagmode = PAGINATION_STATELESS;
|
||||
/* Use plugin state callbacks */
|
||||
if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath,
|
||||
pagmode,
|
||||
offset, limit, &remaining, &xret)) < 0)
|
||||
if ((ret = clixon_pagination_cb_call(h, xpath, pagmode,
|
||||
offset, limit, &remaining,
|
||||
xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
}
|
||||
/* Help function to filter out anything that is outside of xpath */
|
||||
if (filter_xpath_again(h, yspec, xret, xpath, nsc, &x1) < 0)
|
||||
|
|
|
|||
|
|
@ -253,10 +253,6 @@ clixon_plugin_statedata_one(clixon_plugin_t *cp,
|
|||
clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj **xp)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -267,7 +263,7 @@ clixon_plugin_statedata_one(clixon_plugin_t *cp,
|
|||
if ((fn = clixon_plugin_api_get(cp)->ca_statedata) != NULL){
|
||||
if ((x = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
if (fn(h, nsc, xpath, pagmode, offset, limit, remaining, x) < 0){
|
||||
if (fn(h, nsc, xpath, x) < 0){
|
||||
if (clicon_errno < 0)
|
||||
clicon_log(LOG_WARNING, "%s: Internal error: State callback in plugin: %s returned -1 but did not make a clicon_err call",
|
||||
__FUNCTION__, clixon_plugin_name_get(cp));
|
||||
|
|
@ -306,10 +302,6 @@ clixon_plugin_statedata_all(clicon_handle h,
|
|||
yang_stmt *yspec,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj **xret)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -321,8 +313,7 @@ clixon_plugin_statedata_all(clicon_handle h,
|
|||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
||||
if ((ret = clixon_plugin_statedata_one(cp, h, nsc, xpath, pagmode,
|
||||
offset, limit, remaining, &x)) < 0)
|
||||
if ((ret = clixon_plugin_statedata_one(cp, h, nsc, xpath, &x)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if ((cberr = cbuf_new()) == NULL){
|
||||
|
|
@ -455,6 +446,63 @@ clixon_plugin_lockdb_all(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Traverse state data callbacks
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] xpath Registered XPath using canonical prefixes
|
||||
*/
|
||||
int
|
||||
clixon_pagination_cb_call(clicon_handle h,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
pagination_data_t pd = {pagmode, offset, limit, 0, xstate};
|
||||
dispatcher_entry_t *htable = NULL;
|
||||
|
||||
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
|
||||
if (htable && dispatcher_call_handlers(htable, h, xpath, &pd) < 0)
|
||||
goto done;
|
||||
if (remaining)
|
||||
*remaining = pd.pd_remaining;
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Register a state data callback
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] fn Callback
|
||||
* @param[in] xpath Registered XPath using canonical prefixes
|
||||
* @param[in] arg Domain-specific argument to send to callback
|
||||
*/
|
||||
int
|
||||
clixon_pagination_cb_register(clicon_handle h,
|
||||
handler_function fn,
|
||||
char *xpath,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
dispatcher_definition x = {xpath, fn};
|
||||
dispatcher_entry_t *htable = NULL;
|
||||
|
||||
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
|
||||
if (dispatcher_register_handler(&htable, &x) < 0){
|
||||
clicon_err(OE_PLUGIN, errno, "dispatcher");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_ptr_set(h, "pagination-entries", htable) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create and initialize a validate/commit transaction
|
||||
* @retval td New alloced transaction,
|
||||
* @retval NULL Error
|
||||
|
|
@ -885,4 +933,3 @@ plugin_transaction_abort_all(clicon_handle h,
|
|||
retval = 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
* It is up to the validate callbacks to ensure that these changes are OK
|
||||
* It is up to the commit callbacks to enforce these changes in the "state" of
|
||||
* the system.
|
||||
* see also transaction_data in clixon_plugin.h
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t td_id; /* Transaction id */
|
||||
|
|
@ -66,6 +67,21 @@ typedef struct {
|
|||
int td_clen; /* Changed xml vector length */
|
||||
} transaction_data_t;
|
||||
|
||||
/*! Pagination userdata
|
||||
* @param[in] pagmode List pagination mode
|
||||
* @param[in] offset Offset, for list pagination
|
||||
* @param[in] limit Limit, for list pagination
|
||||
* @param[out] remaining Remaining elements (if limit is non-zero)
|
||||
* see also pagination_data in clixon_plugin.h
|
||||
*/
|
||||
typedef struct {
|
||||
pagination_mode_t pd_pagmode; /* Pagination mode, stateless or locked */
|
||||
uint32_t pd_offset; /* Start of pagination interval */
|
||||
uint32_t pd_limit; /* Number of elemenents (limit) */
|
||||
uint32_t pd_remaining; /* If limit, then remaining nr of elements */
|
||||
cxobj *pd_xstate; /* Returned xml state tree */
|
||||
} pagination_data_t;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
|
|
@ -76,11 +92,14 @@ int clixon_plugin_pre_daemon_all(clicon_handle h);
|
|||
int clixon_plugin_daemon_all(clicon_handle h);
|
||||
|
||||
int clixon_plugin_statedata_all(clicon_handle h, yang_stmt *yspec, cvec *nsc, char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset, uint32_t limit, uint32_t *remaining,
|
||||
cxobj **xtop);
|
||||
int clixon_plugin_lockdb_all(clicon_handle h, char *db, int lock, int id);
|
||||
|
||||
int clixon_pagination_cb_register(clicon_handle h, handler_function fn, char *path, void *arg);
|
||||
int clixon_pagination_cb_call(clicon_handle h, char *xpath, pagination_mode_t pagmode,
|
||||
uint32_t offset, uint32_t limit, uint32_t *remaining,
|
||||
cxobj *xstate);
|
||||
|
||||
transaction_data_t * transaction_new(void);
|
||||
int transaction_free(transaction_data_t *);
|
||||
|
||||
|
|
|
|||
|
|
@ -284,3 +284,59 @@ transaction_log(clicon_handle h,
|
|||
cbuf_free(cb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get pagination data: mode parameter
|
||||
*
|
||||
* @param[in] pd Pagination userdata
|
||||
* @retval mode Pagination mode, stateless or locked
|
||||
*/
|
||||
pagination_mode_t
|
||||
pagination_pagmode(pagination_data pd)
|
||||
{
|
||||
return ((pagination_data_t *)pd)->pd_pagmode;
|
||||
}
|
||||
|
||||
/*! Get pagination data: offset parameter
|
||||
*
|
||||
* @param[in] pd Pagination userdata
|
||||
* @retval offset Start of pagination interval
|
||||
*/
|
||||
uint32_t
|
||||
pagination_offset(pagination_data pd)
|
||||
{
|
||||
return ((pagination_data_t *)pd)->pd_offset;
|
||||
}
|
||||
|
||||
/*! Get pagination data: limit parameter
|
||||
*
|
||||
* @param[in] pd Pagination userdata
|
||||
* @retval limit Number of elemenents (limit)
|
||||
*/
|
||||
uint32_t
|
||||
pagination_limit(pagination_data pd)
|
||||
{
|
||||
return ((pagination_data_t *)pd)->pd_limit;
|
||||
}
|
||||
|
||||
/*! Set pagination data: remaining nr of elements
|
||||
*
|
||||
* @param[in] pd Pagination userdata
|
||||
* @param[in] remaining If limit, then remaining nr of elements
|
||||
*/
|
||||
int
|
||||
pagination_remaining_set(pagination_data pd,
|
||||
uint32_t remaining)
|
||||
{
|
||||
return ((pagination_data_t *)pd)->pd_remaining = remaining;
|
||||
}
|
||||
|
||||
/*! Get pagination data: Returned xml state tree
|
||||
*
|
||||
* @param[in] pd Pagination userdata
|
||||
* @retval xstate Returned xml state tree
|
||||
*/
|
||||
cxobj*
|
||||
pagination_xstate(pagination_data pd)
|
||||
{
|
||||
return ((pagination_data_t *)pd)->pd_xstate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
* Prototypes
|
||||
*/
|
||||
/* Transaction callback data accessors for client plugins
|
||||
* (defined in config_dbdep.c)
|
||||
* @see transaction_data_t internal structure
|
||||
*/
|
||||
uint64_t transaction_id(transaction_data td);
|
||||
|
|
@ -65,4 +64,13 @@ size_t transaction_clen(transaction_data td);
|
|||
int transaction_print(FILE *f, transaction_data th);
|
||||
int transaction_log(clicon_handle h, transaction_data th, int level, const char *id);
|
||||
|
||||
/* Pagination callbacks
|
||||
* @see pagination_data_t internal structure
|
||||
*/
|
||||
pagination_mode_t pagination_pagmode(pagination_data pd);
|
||||
uint32_t pagination_offset(pagination_data pd);
|
||||
uint32_t pagination_limit(pagination_data pd);
|
||||
int pagination_remaining_set(pagination_data pd, uint32_t remaining);
|
||||
cxobj *pagination_xstate(pagination_data pd);
|
||||
|
||||
#endif /* _CLIXON_BACKEND_TRANSACTION_H_ */
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
#include <clixon/clixon_backend.h>
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:"
|
||||
#define BACKEND_EXAMPLE_OPTS "rsS:x:iuUt:v:"
|
||||
|
||||
/*! Variable to control if reset code is run.
|
||||
* The reset code inserts "extra XML" which assumes ietf-interfaces is
|
||||
|
|
@ -87,6 +87,13 @@ static int _state = 0;
|
|||
*/
|
||||
static char *_state_file = NULL;
|
||||
|
||||
/*! XPath to register for pagination state XML from file,
|
||||
* if _state is true -- -sS <file> -x <xpath>
|
||||
* Primarily for testing
|
||||
* Start backend with -- -sS <file> -x <xpath>
|
||||
*/
|
||||
static char *_state_xpath = NULL;
|
||||
|
||||
/*! Read state file init on startup instead of on request
|
||||
* Primarily for testing
|
||||
* Start backend with -- -siS <file>
|
||||
|
|
@ -353,10 +360,6 @@ example_copy_extra(clicon_handle h, /* Clicon handle */
|
|||
* @param[in] h Clicon handle
|
||||
* @param[in] nsc External XML namespace context, or NULL
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] pagmode List pagination (not used here)
|
||||
* @param[in] offset Offset, for list pagination
|
||||
* @param[in] limit Limit, for list pagination
|
||||
* @param[out] remaining Remaining elements (if limit is non-zero)
|
||||
* @param[out] xstate XML tree, <config/> on entry.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
|
|
@ -376,10 +379,6 @@ int
|
|||
example_statedata(clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -462,9 +461,6 @@ example_statedata(clicon_handle h,
|
|||
* @param[in] h Clicon handle
|
||||
* @param[in] nsc External XML namespace context, or NULL
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] pagmode List pagination mode
|
||||
* @param[in] offset Offset, for list pagination
|
||||
* @param[in] limit Limit, for list pagination
|
||||
* @param[out] xstate XML tree, <config/> on entry. Copy to this
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
|
|
@ -472,14 +468,10 @@ example_statedata(clicon_handle h,
|
|||
* @see example_statefile where state is programmatically added
|
||||
*/
|
||||
int
|
||||
example_statefile(clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj *xstate)
|
||||
example_statefile(clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **xvec = NULL;
|
||||
|
|
@ -489,8 +481,6 @@ example_statefile(clicon_handle h,
|
|||
yang_stmt *yspec = NULL;
|
||||
FILE *fp = NULL;
|
||||
cxobj *x1;
|
||||
uint32_t lower;
|
||||
uint32_t upper;
|
||||
int ret;
|
||||
|
||||
/* If -S is set, then read state data from file */
|
||||
|
|
@ -511,6 +501,100 @@ example_statefile(clicon_handle h,
|
|||
if (_state_file_cached)
|
||||
_state_xml_cache = xt;
|
||||
}
|
||||
if (_state_file_cached)
|
||||
xt = _state_xml_cache;
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
|
||||
goto done;
|
||||
/* Mark elements to copy:
|
||||
* For every node found in x0, mark the tree as changed
|
||||
*/
|
||||
for (i=0; i<xlen; i++){
|
||||
if ((x1 = xvec[i]) == NULL)
|
||||
break;
|
||||
xml_flag_set(x1, XML_FLAG_MARK);
|
||||
xml_apply_ancestor(x1, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
||||
}
|
||||
if (xml_copy_marked(xt, xstate) < 0) /* Copy the marked elements */
|
||||
goto done;
|
||||
/* Unmark original tree */
|
||||
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
|
||||
goto done;
|
||||
/* Unmark returned state tree */
|
||||
if (xml_apply(xstate, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
|
||||
goto done;
|
||||
if (_state_file_cached)
|
||||
xt = NULL; /* ensure cache is not cleared */
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Example of state pagination callback and how to use pagination_data
|
||||
*
|
||||
* @param[in] h Generic handler
|
||||
* @param[in] xpath Registered XPath using canonical prefixes
|
||||
* @param[in] userargs Per-call user arguments
|
||||
* @param[in] arg Per-path user argument
|
||||
*/
|
||||
int
|
||||
example_pagination(void *h0,
|
||||
char *xpath,
|
||||
pagination_data pd,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
clicon_handle h = (clicon_handle)h0;
|
||||
pagination_mode_t pagmode;
|
||||
uint32_t offset;
|
||||
uint32_t limit;
|
||||
uint32_t remaining;
|
||||
cxobj *xstate;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen = 0;
|
||||
int i;
|
||||
cxobj *xt = NULL;
|
||||
yang_stmt *yspec = NULL;
|
||||
FILE *fp = NULL;
|
||||
cxobj *x1;
|
||||
uint32_t lower;
|
||||
uint32_t upper;
|
||||
int ret;
|
||||
cvec *nsc;
|
||||
|
||||
/* If -S is set, then read state data from file */
|
||||
if (!_state || !_state_file)
|
||||
goto ok;
|
||||
|
||||
pagmode = pagination_pagmode(pd);
|
||||
offset = pagination_offset(pd);
|
||||
limit = pagination_limit(pd);
|
||||
xstate = pagination_xstate(pd);
|
||||
|
||||
/* Get canonical namespace context */
|
||||
if (xml_nsctx_yangspec(yspec, &nsc) < 0)
|
||||
goto done;
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
/* Read state file if either not cached, or the cache is NULL */
|
||||
if (_state_file_cached == 0 ||
|
||||
_state_xml_cache == NULL){
|
||||
if ((fp = fopen(_state_file, "r")) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
|
||||
goto done;
|
||||
}
|
||||
if ((xt = xml_new("config", NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
if ((ret = clixon_xml_parse_file(fp, YB_MODULE, yspec, &xt, NULL)) < 0)
|
||||
goto done;
|
||||
if (_state_file_cached)
|
||||
_state_xml_cache = xt;
|
||||
}
|
||||
if (_state_file_cached)
|
||||
xt = _state_xml_cache;
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
|
||||
|
|
@ -530,7 +614,8 @@ example_statefile(clicon_handle h,
|
|||
else{
|
||||
if ((upper = offset+limit)>xlen)
|
||||
upper = xlen;
|
||||
*remaining = xlen - upper;
|
||||
remaining = xlen - upper;
|
||||
pagination_remaining_set(pd, remaining);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -1166,7 +1251,9 @@ clixon_plugin_init(clicon_handle h)
|
|||
break;
|
||||
case 'S': /* state file (requires -s) */
|
||||
_state_file = optarg;
|
||||
api.ca_statedata = example_statefile; /* Switch state data callback */
|
||||
break;
|
||||
case 'x': /* state xpath (requires -sS) */
|
||||
_state_xpath = optarg;
|
||||
break;
|
||||
case 'i': /* read state file on init not by request (requires -sS <file> */
|
||||
_state_file_cached = 1;
|
||||
|
|
@ -1185,6 +1272,18 @@ clixon_plugin_init(clicon_handle h)
|
|||
break;
|
||||
}
|
||||
|
||||
if (_state_file){
|
||||
api.ca_statedata = example_statefile; /* Switch state data callback */
|
||||
if (_state_xpath){
|
||||
/* State pagination callbacks */
|
||||
if (clixon_pagination_cb_register(h,
|
||||
example_pagination,
|
||||
_state_xpath,
|
||||
NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Example stream initialization:
|
||||
* 1) Register EXAMPLE stream
|
||||
* 2) setup timer for notifications, so something happens on stream
|
||||
|
|
|
|||
|
|
@ -180,10 +180,6 @@ int
|
|||
nacm_statedata(clicon_handle h,
|
||||
cvec *nsc,
|
||||
char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset,
|
||||
uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -210,7 +206,7 @@ static clixon_plugin_api api = {
|
|||
clixon_plugin_init, /* init */
|
||||
NULL, /* start */
|
||||
NULL, /* exit */
|
||||
.ca_statedata=nacm_statedata, /* statedata */
|
||||
.ca_statedata=nacm_statedata, /* statedata */
|
||||
.ca_trans_begin=nacm_begin, /* trans begin */
|
||||
.ca_trans_validate=nacm_validate, /* trans validate */
|
||||
.ca_trans_complete=nacm_complete, /* trans complete */
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ extern "C" {
|
|||
#include <clixon/clixon_xml_nsctx.h>
|
||||
#include <clixon/clixon_xml_vec.h>
|
||||
#include <clixon/clixon_client.h>
|
||||
#include <clixon/clixon_dispatcher.h>
|
||||
|
||||
/*
|
||||
* Global variables generated by Makefile
|
||||
|
|
|
|||
|
|
@ -61,10 +61,15 @@ typedef struct {
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
/* Generic clixon data API the form <name>=<val> where <val> is string */
|
||||
int clicon_data_get(clicon_handle h, const char *name, char **val);
|
||||
int clicon_data_set(clicon_handle h, const char *name, char *val);
|
||||
int clicon_data_del(clicon_handle h, const char *name);
|
||||
|
||||
int clicon_ptr_get(clicon_handle h, const char *name, void **ptr);
|
||||
int clicon_ptr_set(clicon_handle h, const char *name, void *ptr);
|
||||
int clicon_ptr_del(clicon_handle h, const char *name);
|
||||
|
||||
cvec *clicon_data_cvec_get(clicon_handle h, const char *name);
|
||||
int clicon_data_cvec_set(clicon_handle h, const char *name, cvec *cvv);
|
||||
int clicon_data_cvec_del(clicon_handle h, const char *name);
|
||||
|
|
|
|||
75
lib/clixon/clixon_dispatcher.h
Normal file
75
lib/clixon/clixon_dispatcher.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2021 Rubicon Communications LLC (Netgate)
|
||||
* @see https://github.com/dcornejo/dispatcher
|
||||
*/
|
||||
|
||||
#ifndef DISPATCH_DISPATCHER_H
|
||||
#define DISPATCH_DISPATCHER_H
|
||||
|
||||
/*! prototype for a function to handle a path
|
||||
* minimally needs the path it's working on, but probably
|
||||
* we want to hand down cached data somehow
|
||||
* @param[in] h Generic handler
|
||||
* @param[in] xpath Registered XPath using canonical prefixes
|
||||
* @param[in] userargs Per-call user arguments
|
||||
* @param[in] arg Per-path user argument
|
||||
*/
|
||||
typedef int (*handler_function)(void *handle, char *path, void *userargs, void *arg);
|
||||
|
||||
/*
|
||||
* this structure is used to map a handler to a path
|
||||
*/
|
||||
typedef struct {
|
||||
char *dd_path;
|
||||
handler_function dd_handler;
|
||||
} dispatcher_definition;
|
||||
|
||||
/*
|
||||
* the dispatcher_entry_t is the structure created from
|
||||
* the registered dispatcher_definitions
|
||||
*/
|
||||
struct _dispatcher_entry;
|
||||
typedef struct _dispatcher_entry dispatcher_entry_t;
|
||||
|
||||
struct _dispatcher_entry {
|
||||
/*
|
||||
* the name of this node, NOT the complete path
|
||||
*/
|
||||
char *node_name;
|
||||
|
||||
/*
|
||||
* peer points at peer to the right of this one
|
||||
* if NULL then this is the rightmost and last on list
|
||||
*/
|
||||
dispatcher_entry_t *peer;
|
||||
|
||||
/*
|
||||
* peer_head points at leftmost peer at this level
|
||||
* if NULL, then this is the leftmost and first on the list
|
||||
*/
|
||||
dispatcher_entry_t *peer_head;
|
||||
|
||||
/*
|
||||
* points at peer_head of children list
|
||||
* if NULL, then no children
|
||||
*/
|
||||
dispatcher_entry_t *children;
|
||||
|
||||
/*
|
||||
* pointer to handler function for this node
|
||||
*/
|
||||
handler_function handler;
|
||||
|
||||
/*
|
||||
* End-user argument
|
||||
*/
|
||||
void *arg;
|
||||
};
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int dispatcher_register_handler(dispatcher_entry_t **root, dispatcher_definition *x);
|
||||
int dispatcher_call_handlers(dispatcher_entry_t *root, void *handle, char *path, void *user_args);
|
||||
|
||||
#endif /* DISPATCH_DISPATCHER_H */
|
||||
|
|
@ -217,24 +217,21 @@ enum pagination_mode{
|
|||
typedef enum pagination_mode pagination_mode_t;
|
||||
|
||||
/* Plugin statedata
|
||||
* @param[in] Clicon handle
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xpath Part of state requested
|
||||
* @param[in] nsc XPATH namespace context.
|
||||
* @param[in] pagmode List pagination mode
|
||||
* @param[in] offset Offset, for list pagination
|
||||
* @param[in] limit Limit, for list pagination
|
||||
* @param[out] remaining Remaining elements (if limit is non-zero)
|
||||
* @param[out] xtop XML tree where statedata is added
|
||||
* @retval -1 Fatal error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
typedef int (plgstatedata_t)(clicon_handle h, cvec *nsc, char *xpath,
|
||||
pagination_mode_t pagmode,
|
||||
uint32_t offset, uint32_t limit,
|
||||
uint32_t *remaining,
|
||||
cxobj *xtop);
|
||||
typedef int (plgstatedata_t)(clicon_handle h, cvec *nsc, char *xpath, cxobj *xtop);
|
||||
|
||||
/*! Lock databse status has changed status
|
||||
/* Pagination-data type
|
||||
* @see pagination_data_t in clixon_backend_transaction.h for full pagination API
|
||||
*/
|
||||
typedef void *pagination_data;
|
||||
|
||||
/*! Lock database status has changed status
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] db Database name (eg "running")
|
||||
* @param[in] lock Lock status: 0: unlocked, 1: locked
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
|
|||
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \
|
||||
clixon_xpath_optimize.c clixon_xpath_yang.c \
|
||||
clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \
|
||||
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c
|
||||
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c \
|
||||
clixon_dispatcher.c
|
||||
|
||||
YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
|
||||
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \
|
||||
|
|
|
|||
|
|
@ -134,6 +134,66 @@ clicon_data_del(clicon_handle h,
|
|||
return clicon_hash_del(cdat, (char*)name);
|
||||
}
|
||||
|
||||
/*! Get generic clixon data on the form <name>=<ptr> where <ptr> is void*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name Data name
|
||||
* @param[out] val Data value as string
|
||||
* @retval 0 OK
|
||||
* @retval -1 Not found (or error)
|
||||
* @see clicon_option_str
|
||||
*/
|
||||
int
|
||||
clicon_ptr_get(clicon_handle h,
|
||||
const char *name,
|
||||
void **ptr)
|
||||
{
|
||||
clicon_hash_t *cdat = clicon_data(h);
|
||||
void *p;
|
||||
size_t vlen;
|
||||
|
||||
if (clicon_hash_lookup(cdat, (char*)name) == NULL)
|
||||
return -1;
|
||||
if (ptr){
|
||||
p = clicon_hash_value(cdat, (char*)name, &vlen);
|
||||
memcpy(ptr, p, vlen);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Set generic clixon data on the form <name>=<ptr> where <ptr> is void*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name Data name
|
||||
* @param[in] val Data value as null-terminated string
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see clicon_option_str_set
|
||||
*/
|
||||
int
|
||||
clicon_ptr_set(clicon_handle h,
|
||||
const char *name,
|
||||
void *ptr)
|
||||
{
|
||||
clicon_hash_t *cdat = clicon_data(h);
|
||||
|
||||
return clicon_hash_add(cdat, (char*)name, &ptr, sizeof(ptr))==NULL?-1:0;
|
||||
}
|
||||
|
||||
/*! Delete generic clixon data
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name Data name
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see clicon_option_del
|
||||
*/
|
||||
int
|
||||
clicon_ptr_del(clicon_handle h,
|
||||
const char *name)
|
||||
{
|
||||
clicon_hash_t *cdat = clicon_data(h);
|
||||
|
||||
return clicon_hash_del(cdat, (char*)name);
|
||||
}
|
||||
|
||||
/*! Get generic cligen variable vector (cvv) on the form <name>=<val> where <val> is cvv
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
|
|
@ -201,7 +261,7 @@ clicon_data_cvec_del(clicon_handle h,
|
|||
return clicon_hash_del(cdat, (char*)name);
|
||||
}
|
||||
|
||||
/*!
|
||||
/*! Get data yangspec, yspec
|
||||
* @param[in] h Clicon handle
|
||||
* @retval yspec Yang spec
|
||||
* @see clicon_config_yang for the configuration yang
|
||||
|
|
|
|||
411
lib/src/clixon_dispatcher.c
Normal file
411
lib/src/clixon_dispatcher.c
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Copyright 2021 Rubicon Communications LLC (Netgate)
|
||||
* @see https://github.com/dcornejo/dispatcher
|
||||
*/
|
||||
|
||||
/*
|
||||
* we start with a series of dispatcher_definitions, which are a
|
||||
* path and handler.
|
||||
*
|
||||
* we break the path up into elements and build a tree out of them
|
||||
* example:
|
||||
*
|
||||
* we start with two paths /a/b/c and /a/d with handler_c() and
|
||||
* handler_d() as their handlers respectively.
|
||||
*
|
||||
* this produces a tree like this:
|
||||
*
|
||||
* [/] root_handler()
|
||||
* [a] NULL
|
||||
* [b] NULL
|
||||
* [c] handler_c()
|
||||
* [d] handler_d()
|
||||
*
|
||||
* NULL means that there is no handler defined - if the terminal
|
||||
* element of the path has a NULL handler then you look for the
|
||||
* closest ancestor that does.
|
||||
*
|
||||
* for example, if I lookup /a/b I get back a pointer to root_handler()
|
||||
* if i lookup /a/d, I get handler_d().
|
||||
*
|
||||
* if a element has a key (/a/b=c) then the list element is
|
||||
* marked with an = sign and without the key
|
||||
* so /a/b=c creates multiple entries:
|
||||
*
|
||||
* [/]
|
||||
* [a]
|
||||
* [b=]
|
||||
* [b]
|
||||
*
|
||||
* NOTE 1: there is not a mechanism to free the created structures since
|
||||
* it is intended that this tree is created only at startup. if use case
|
||||
* changes, this function is trivial.
|
||||
*
|
||||
* NOTE 2: there is no attempt to optimize list searching here, sorry. I
|
||||
* do not think that the known use cases will get big enough to make the
|
||||
* tree get too large. I do not recommend that you encode every possible
|
||||
* path, just top level key handlers.
|
||||
*
|
||||
* there are 2 functions to the API:
|
||||
* clixon_register_handler(): build the dispatcher table
|
||||
* clixon_call_handlers(): query the dispatcher table
|
||||
*/
|
||||
|
||||
/*
|
||||
* Important for writing handlers: a handler must return a complete
|
||||
* valid response. It must operate in isolation, it must not expect
|
||||
* any ordering in the calls and [under review] it should not call
|
||||
* another handler directly or indirectly. Responses must me bound
|
||||
* to a yang model and properly sorted and indexed.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "clixon_dispatcher.h"
|
||||
|
||||
/* ===== utility routines ==== */
|
||||
|
||||
#define PATH_CHUNKS 32
|
||||
|
||||
/**
|
||||
* spilt a path into elements
|
||||
*
|
||||
* given an api-path, break it up into chunks separated by '/'
|
||||
* characters. it is expected that api-paths are URI encoded, so no need
|
||||
* to deal with unescaped special characters like ', ", and /
|
||||
*
|
||||
* @param path [input] path string
|
||||
* @param plist [output] pointer to split path array
|
||||
* @param plist_len [output] pointer to storage space for path array length
|
||||
*/
|
||||
|
||||
static void split_path(char *path, char ***plist, size_t *plist_len)
|
||||
{
|
||||
size_t allocated = PATH_CHUNKS;
|
||||
|
||||
/* don't modify the original copy */
|
||||
char *work = strdup(path);
|
||||
|
||||
char **list = malloc(allocated * sizeof(char *));
|
||||
memset(list, 0, allocated * sizeof(char *));
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
char *ptr = work;
|
||||
if (*ptr == '/') {
|
||||
char *new_element = strdup("/");
|
||||
list[len++] = new_element;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
ptr = strtok(ptr, "/");
|
||||
|
||||
while (ptr != NULL) {
|
||||
if (len > allocated) {
|
||||
/* we've run out of space, allocate a bigger list */
|
||||
allocated += PATH_CHUNKS;
|
||||
list = realloc(list, allocated * sizeof(char *));
|
||||
}
|
||||
|
||||
char *new_element = strdup(ptr);
|
||||
list[len++] = new_element;
|
||||
|
||||
ptr = strtok(NULL, "/");
|
||||
}
|
||||
|
||||
*plist = list;
|
||||
*plist_len = len;
|
||||
|
||||
free(work);
|
||||
}
|
||||
|
||||
/**
|
||||
* free a split path structure
|
||||
*
|
||||
* @param list [input] pointer to split path array
|
||||
* @param len [input] length of split path array
|
||||
*/
|
||||
|
||||
static void split_path_free(char **list, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
free(list[i]);
|
||||
}
|
||||
free(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* find a peer of this node by name
|
||||
* search through the list pointed at by peer
|
||||
*
|
||||
* @param node [input] pointer to a node in the peer list
|
||||
* @param node_name [input] name of node we're looking for
|
||||
* @return pointer to found node or NULL
|
||||
*/
|
||||
|
||||
static dispatcher_entry_t *find_peer(dispatcher_entry_t *node, char *node_name)
|
||||
{
|
||||
if ((node == NULL) || (node_name == NULL)) {
|
||||
/* protect against idiot users */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dispatcher_entry_t *i = node->peer_head;
|
||||
|
||||
while (i != NULL) {
|
||||
if (strcmp(node_name, i->node_name) == 0) {
|
||||
break;
|
||||
}
|
||||
i = i->peer;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a node as the last node in peer list
|
||||
*
|
||||
* @param node [input] pointer to an element of the peer list
|
||||
* @param name [input] name of new node
|
||||
* @return pointer to added/existing node
|
||||
*/
|
||||
|
||||
static dispatcher_entry_t *add_peer_node(dispatcher_entry_t *node, char *name)
|
||||
{
|
||||
dispatcher_entry_t *new_node = malloc(sizeof(dispatcher_entry_t));
|
||||
memset(new_node, 0, sizeof(dispatcher_entry_t));
|
||||
|
||||
if (node == NULL) {
|
||||
/* this is a new node */
|
||||
|
||||
new_node->node_name = strdup(name);
|
||||
new_node->peer = NULL;
|
||||
new_node->children = NULL;
|
||||
new_node->peer_head = new_node;
|
||||
|
||||
return new_node;
|
||||
} else {
|
||||
/* possibly adding to the list */
|
||||
|
||||
/* search for existing, or get tail end of list */
|
||||
dispatcher_entry_t *eptr = node->peer_head;
|
||||
while (eptr->peer != NULL) {
|
||||
if (strcmp(eptr->node_name, name) == 0) {
|
||||
return eptr;
|
||||
}
|
||||
eptr = eptr->peer;
|
||||
}
|
||||
|
||||
// if eptr->node_name == name, we done
|
||||
if (strcmp(eptr->node_name, name) == 0) {
|
||||
return eptr;
|
||||
}
|
||||
|
||||
new_node->node_name = strdup(name);
|
||||
new_node->peer = NULL;
|
||||
new_node->children = NULL;
|
||||
new_node->peer_head = node->peer_head;
|
||||
|
||||
eptr->peer = new_node;
|
||||
|
||||
return new_node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a node as a child of this node
|
||||
*
|
||||
* this is different from add_peer_node() in that it returns a
|
||||
* pointer to the head_peer of the children list where the node was
|
||||
* added.
|
||||
*
|
||||
* @param node [input] pointer to parent node of children list
|
||||
* @param name [input] name of child node
|
||||
* @return pointer to head of children list
|
||||
*/
|
||||
|
||||
static dispatcher_entry_t *add_child_node(dispatcher_entry_t *node, char *name)
|
||||
{
|
||||
dispatcher_entry_t *child_ptr = add_peer_node(node->children, name);
|
||||
node->children = child_ptr->peer_head;
|
||||
|
||||
return child_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param root
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
|
||||
static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
|
||||
{
|
||||
char **split_path_list = NULL;
|
||||
size_t split_path_len = 0;
|
||||
dispatcher_entry_t *ptr = root;
|
||||
dispatcher_entry_t *best = root;
|
||||
|
||||
/* cut the path up into individual elements */
|
||||
split_path(path, &split_path_list, &split_path_len);
|
||||
|
||||
/* some elements may have keys defined, strip them off */
|
||||
for (int i = 0; i < split_path_len; i++) {
|
||||
char *kptr = strchr(split_path_list[i], '=');
|
||||
|
||||
if ((kptr != NULL) && (*kptr == '=')) {
|
||||
*(kptr + 1) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* search down the tree */
|
||||
for (int i = 0; i < split_path_len; i++) {
|
||||
|
||||
char *query = split_path_list[i];
|
||||
ptr = find_peer(ptr, query);
|
||||
|
||||
if (ptr == NULL) {
|
||||
/* we ran out of matches, use last found handler */
|
||||
return best;
|
||||
}
|
||||
if (ptr->handler != NULL) {
|
||||
/* if handler is defined, save it */
|
||||
best = ptr;
|
||||
}
|
||||
|
||||
/* skip to next element */
|
||||
ptr = ptr->children;
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* given a pointer to an entry, call the handler and all
|
||||
* descendant and peer handlers.
|
||||
*
|
||||
* @param entry
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
static int
|
||||
call_handler_helper(dispatcher_entry_t *entry,
|
||||
void *handle,
|
||||
char *path,
|
||||
void *user_args)
|
||||
{
|
||||
if (entry->children != NULL) {
|
||||
call_handler_helper(entry->children, handle, path, user_args);
|
||||
}
|
||||
if (entry->peer != NULL) {
|
||||
call_handler_helper(entry->peer, handle, path, user_args);
|
||||
}
|
||||
if (entry->handler != NULL) {
|
||||
(entry->handler)(handle, path, user_args, entry->arg);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ===== PUBLIC API FUNCTIONS =====
|
||||
*/
|
||||
|
||||
/*! Register a dispatcher handler
|
||||
*
|
||||
* called from initialization code to build a dispatcher tree
|
||||
*
|
||||
* @param[in] root Pointer to pointer to dispatch tree
|
||||
* @param[in] x Handler to registration data
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
dispatcher_register_handler(dispatcher_entry_t **root,
|
||||
dispatcher_definition *x)
|
||||
{
|
||||
char **split_path_list = NULL;
|
||||
size_t split_path_len = 0;
|
||||
|
||||
if (*x->dd_path != '/') {
|
||||
errno = EINVAL;
|
||||
fprintf(stderr, "%s: part '%s' must start at root\n", __func__, x->dd_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* get the path from the dispatcher_definition, break it
|
||||
* up to create the elements of the dispatcher table
|
||||
*/
|
||||
split_path(x->dd_path, &split_path_list, &split_path_len);
|
||||
|
||||
/*
|
||||
* the first element is always a peer to the top level
|
||||
*/
|
||||
dispatcher_entry_t *ptr = *root;
|
||||
|
||||
ptr = add_peer_node(ptr, split_path_list[0]);
|
||||
if (*root == NULL) {
|
||||
*root = ptr;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < split_path_len; i++) {
|
||||
ptr = add_child_node(ptr, split_path_list[i]);
|
||||
}
|
||||
|
||||
/* when we get here, ptr points at last entry added */
|
||||
if (x->dd_handler != NULL) {
|
||||
/*
|
||||
* we're adding/changing a handler
|
||||
* you could make this an error optionally
|
||||
*/
|
||||
if (ptr->handler != NULL) {
|
||||
printf("%s: warning: replacing existing handler: (%s) %p -> %p\n", __func__,
|
||||
ptr->node_name, ptr->handler, x->dd_handler);
|
||||
}
|
||||
ptr->handler = x->dd_handler;
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
split_path_free(split_path_list, split_path_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Call the handler and all its descendant handlers
|
||||
*
|
||||
* NOTE: There is no guarantee of the order in which handlers
|
||||
* are called! Any handler must assume that it is called in
|
||||
* isolation, even if this duplicates work. The right to
|
||||
* reorder calls by this code is reserved.
|
||||
*
|
||||
* @param[in] handle
|
||||
* @param[in] root
|
||||
* @param[in] path
|
||||
* @retval 1 OK
|
||||
* @retval 0 Invalid
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
dispatcher_call_handlers(dispatcher_entry_t *root,
|
||||
void *handle,
|
||||
char *path,
|
||||
void *user_args)
|
||||
{
|
||||
int ret = 0;
|
||||
dispatcher_entry_t *best = get_entry(root, path);
|
||||
|
||||
if (best->children != NULL) {
|
||||
call_handler_helper(best->children, handle, path, user_args);
|
||||
}
|
||||
if (best->handler != NULL) {
|
||||
ret = (*best->handler)(handle, path, user_args, best->arg);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
# Example-social from draft-netconf-list-pagination-00.txt appendix A.1
|
||||
# Assumes variable fexample is set to name of yang file
|
||||
# Note audit-logs/audit-log/outcome is changed from mandatory to default
|
||||
# Also: leaf-list member/state/numbers is added
|
||||
|
||||
cat <<EOF > $fexample
|
||||
module example-social {
|
||||
|
|
@ -214,6 +215,10 @@ cat <<EOF > $fexample
|
|||
config false;
|
||||
description
|
||||
"Operational state members values.";
|
||||
leaf-list numbers {
|
||||
description "config false extension";
|
||||
type int32;
|
||||
}
|
||||
leaf joined {
|
||||
type yang:date-and-time;
|
||||
mandatory true;
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ if [ $BE -ne 0 ]; then
|
|||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg -- -sS $fstate"
|
||||
start_backend -s init -f $cfg -- -sS $fstate
|
||||
new "start backend -s init -f $cfg -- -sS $fstate -x /lib:global-state"
|
||||
start_backend -s init -f $cfg -- -sS $fstate -x /lib:global-state
|
||||
fi
|
||||
new "waiting"
|
||||
wait_backend
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# List pagination tests loosely based on draft-wwlh-netconf-list-pagination-00
|
||||
# The example-social yang file is used
|
||||
# Three tests to get state pagination data:
|
||||
# 1. NETCONF get a specific list (alice->numbers)
|
||||
# 2. NETCONF get two listsspecific list (alice+bob->numbers)
|
||||
# 3. CLI get audit logs (only interactive)
|
||||
# This tests contains a large state list: audit-logs from the example
|
||||
# Only CLI is used
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
echo "...skipped: Must run interactvely"
|
||||
if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf.xml
|
||||
fexample=$dir/example-social.yang
|
||||
fstate=$dir/mystate.xml
|
||||
|
||||
xpath=/es:audit-logs/es:audit-log
|
||||
|
||||
# For 1M test,m use an external file since the generation takes considerable time
|
||||
#fstate=~/tmp/mystate.xml
|
||||
|
||||
|
|
@ -26,8 +29,7 @@ fstate=$dir/mystate.xml
|
|||
: ${validatexml:=false}
|
||||
|
||||
# Number of audit-log entries
|
||||
: ${perfnr:=20000}
|
||||
|
||||
: ${perfnr:=1000}
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
|
|
@ -51,8 +53,31 @@ cat <<EOF > $cfg
|
|||
EOF
|
||||
|
||||
# See draft-wwlh-netconf-list-pagination-00 A.2 (only stats and audit-log)
|
||||
# XXX members not currently used, only audit-logs as generated below
|
||||
cat<<EOF > $fstate
|
||||
<members xmlns="http://example.com/ns/example-social">
|
||||
<member>
|
||||
<member-id>alice</member-id>
|
||||
<stats>
|
||||
<numbers>3</numbers>
|
||||
<numbers>4</numbers>
|
||||
<numbers>5</numbers>
|
||||
<numbers>6</numbers>
|
||||
<numbers>7</numbers>
|
||||
<numbers>8</numbers>
|
||||
</stats>
|
||||
</member>
|
||||
<member>
|
||||
<member-id>bob</member-id>
|
||||
<stats>
|
||||
<numbers>13</numbers>
|
||||
<numbers>14</numbers>
|
||||
<numbers>15</numbers>
|
||||
<numbers>16</numbers>
|
||||
<numbers>17</numbers>
|
||||
<numbers>18</numbers>
|
||||
</stats>
|
||||
</member>
|
||||
</members>
|
||||
EOF
|
||||
|
||||
# Append generated state data to $fstate file
|
||||
|
|
@ -68,44 +93,82 @@ for (( i=0; i<$perfnr; i++ )); do
|
|||
echo " <request>POST</request>" >> $fstate
|
||||
echo " </audit-log>" >> $fstate
|
||||
done
|
||||
|
||||
echo -n "</audit-logs>" >> $fstate # No CR
|
||||
|
||||
new "test params: -f $cfg -s init -- -siS $fstate"
|
||||
# start backend with specific xpath
|
||||
function testrun_start()
|
||||
{
|
||||
xpath=$1
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
new "test params: -f $cfg -s init -- -siS $fstate -x $xpath"
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill -f clixon_backend # to be sure
|
||||
|
||||
new "start backend -s init -f $cfg -- -siS $fstate -X $xpath"
|
||||
start_backend -s init -f $cfg -- -siS $fstate -x $xpath
|
||||
fi
|
||||
sudo pkill -f clixon_backend # to be sure
|
||||
|
||||
new "start backend -s init -f $cfg -- -siS $fstate"
|
||||
start_backend -s init -f $cfg -- -siS $fstate
|
||||
fi
|
||||
new "wait backend"
|
||||
wait_backend
|
||||
}
|
||||
|
||||
new "wait backend"
|
||||
wait_backend
|
||||
function testrun_stop()
|
||||
{
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
fi
|
||||
}
|
||||
|
||||
testrun_start "/es:members/es:member[es:member-id='alice']/es:stats/es:numbers"
|
||||
|
||||
new "NETCONF get leaf-list member/numbers 0-10 alice"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"$xpath\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination><offset xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">0</offset><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">10</limit></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><stats><numbers>3</numbers><numbers>4</numbers><numbers>5</numbers><numbers>6</numbers><numbers>7</numbers><numbers>8</numbers></stats></member></members></data></rpc-reply>]]>]]>$"
|
||||
|
||||
# negative
|
||||
new "NETCONF get container, expect fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"/es:members/es:member[es:member-id='alice']/es:stats\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination><offset xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">0</offset><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">10</limit></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>invalid-value</error-tag><error-severity>error</error-severity><error-message>list-pagination is enabled but target is not list or leaf-list</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
testrun_stop
|
||||
|
||||
#----------------------------
|
||||
testrun_start "/es:members/es:member/es:stats/es:numbers"
|
||||
|
||||
new "NETCONF get leaf-list member/numbers 0-10 alice"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"$xpath\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination><offset xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">0</offset><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">10</limit></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><stats><numbers>3</numbers><numbers>4</numbers><numbers>5</numbers><numbers>6</numbers><numbers>7</numbers><numbers>8</numbers></stats></member><member><member-id>bob</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><stats><numbers>13</numbers><numbers>14</numbers><numbers>15</numbers><numbers>16</numbers></stats></member></members></data></rpc-reply>]]>]]>$"
|
||||
|
||||
testrun_stop
|
||||
|
||||
#----------------------------
|
||||
|
||||
echo "...skipped: Must run interactvely"
|
||||
if false; then
|
||||
testrun_start "/es:audit-logs/es:audit-log"
|
||||
|
||||
# XXX How to run without using a terminal? Maybe use expect/unbuffer
|
||||
new "cli show"
|
||||
echo "$clixon_cli -1 -f $cfg -l o show pagination xpath /es:audit-logs/es:audit-log cli"
|
||||
$clixon_cli -1 -f $cfg -l o show pagination xpath /es:audit-logs/es:audit-log cli
|
||||
#expectpart "$(echo -n | unbuffer -p $clixon_cli -1 -f $cfg -l o show pagination xpath /es:audit-logs/es:audit-log cli)" 0 foo
|
||||
$clixon_cli -1 -f $cfg -l o show pagination xpath $xpath cli
|
||||
#expectpart "$(echo -n | unbuffer -p $clixon_cli -1 -f $cfg -l o show pagination xpath $xpath cli)" 0 foo
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
fi
|
||||
testrun_stop
|
||||
|
||||
fi # interactive
|
||||
|
||||
unset validatexml
|
||||
unset perfnr
|
||||
unset xpath
|
||||
|
||||
rm -rf $dir
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue