Very large commit for upcoming 4.4 release
Major New features
* New and updated search functions using xpath, api-path and instance-id
* New search functions using api-path and instance_id:
* C search functions: `clixon_find_instance_id()` and `clixon_find_api_path()`
* Binary search optimization in lists for indexed leafs in all three formats.
* This improves search performance to O(logN) which is drastical improvements for large lists.
* You can also register explicit indexes for making binary search (not only list keys)
* For more info, see docs at [paths](https://clixon-docs.readthedocs.io/en/latest/paths.html) and
[search](https://clixon-docs.readthedocs.io/en/latest/xml.html#searching-in-xml)
API changes on existing features (you may need to change your code)
* On failed validation of leafrefs, error message changed from: `No such leaf` to `No leaf <name> matching path <path>`.
* CLI Error message (clicon_rpc_generate_error()) changed when backend returns netconf error to be more descriptive:
* Original: `Config error: Validate failed. Edit and try again or discard changes: Invalid argument`
* New (example): `Netconf error: application operation-failed Identityref validation failed, undefined not derived from acl-base . Validate failed. Edit and try again or discard changes"
Minor changes
* Test framework
* Added `-- -S <file>` command-line to main example to be able to return any state to main example.
* Added `test/cicd` test scripts for running on a set of other hosts
* C-code restructuring
* clixon_yang.c partitioned and moved code into clixon_yang_parse_lib.c and clixon_yang_module.c and move back some code from clixon_yang_type.c.
* partly to reduce size, but most important to limit code that accesses internal yang structures, only clixon_yang.c does this now.
This commit is contained in:
parent
e8ae628d06
commit
19e21be0bc
132 changed files with 6241 additions and 2332 deletions
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -252,6 +253,7 @@ client_get_streams(clicon_handle h,
|
|||
* @param[in] h Clicon handle
|
||||
* @param[in] xpath Xpath selection, not used but may be to filter early
|
||||
* @param[in] nsc XML Namespace context for xpath
|
||||
* @param[in] content config/state or both
|
||||
* @param[in,out] xret Existing XML tree, merge x into this
|
||||
* @retval -1 Error (fatal)
|
||||
* @retval 0 Statedata callback failed (clicon_err called)
|
||||
|
|
@ -261,17 +263,15 @@ static int
|
|||
client_statedata(clicon_handle h,
|
||||
char *xpath,
|
||||
cvec *nsc,
|
||||
netconf_content content,
|
||||
cxobj **xret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
int i;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *ymod;
|
||||
int ret;
|
||||
char *namespace;
|
||||
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
|
|
@ -320,32 +320,9 @@ client_statedata(clicon_handle h,
|
|||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Code complex to filter out anything that is outside of xpath
|
||||
* Actually this is a safety catch, should really be done in plugins
|
||||
* and modules_state functions.
|
||||
*/
|
||||
if (xpath_vec(*xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
/* If vectors are specified then mark the nodes found and
|
||||
* then filter out everything else,
|
||||
* otherwise return complete tree.
|
||||
*/
|
||||
if (xvec != NULL){
|
||||
for (i=0; i<xlen; i++)
|
||||
xml_flag_set(xvec[i], XML_FLAG_MARK);
|
||||
}
|
||||
/* Remove everything that is not marked */
|
||||
if (!xml_flag(*xret, XML_FLAG_MARK))
|
||||
if (xml_tree_prune_flagged_sub(*xret, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
/* reset flag */
|
||||
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||
goto done;
|
||||
retval = 1; /* OK */
|
||||
done:
|
||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
|
|
@ -354,73 +331,35 @@ client_statedata(clicon_handle h,
|
|||
|
||||
/*! Retrieve all or part of a specified configuration.
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xe Request: <rpc><xn></rpc>
|
||||
* Function reused from both from_client_get() and from_client_get_config
|
||||
* @param[in] yspec
|
||||
* @param[in] db
|
||||
* @param[in] xpath
|
||||
* @param[in] username
|
||||
* @param[in] content
|
||||
* @param[in] depth
|
||||
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
|
||||
* @param[in] arg client-entry
|
||||
* @param[in] regarg User argument given at rpc_callback_register()
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see from_client_get
|
||||
*/
|
||||
static int
|
||||
from_client_get_config(clicon_handle h,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
client_config_only(clicon_handle h,
|
||||
cvec *nsc,
|
||||
yang_stmt *yspec,
|
||||
char *db,
|
||||
char *xpath,
|
||||
char *username,
|
||||
int32_t depth,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
char *db;
|
||||
cxobj *xfilter;
|
||||
char *xpath = NULL;
|
||||
cxobj *xret = NULL;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
cxobj *xnacm = NULL;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
int ret;
|
||||
char *username;
|
||||
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
|
||||
yang_stmt *yspec;
|
||||
|
||||
username = clicon_username_get(h);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec9");
|
||||
goto done;
|
||||
}
|
||||
if ((db = netconf_db_find(xe, "source")) == NULL){
|
||||
clicon_err(OE_XML, 0, "db not found");
|
||||
goto done;
|
||||
}
|
||||
if (xmldb_validate_db(db) < 0){
|
||||
if ((cbx = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbx, "No such database: %s", db);
|
||||
if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((xfilter = xml_find(xe, "filter")) != NULL){
|
||||
char *xpath0;
|
||||
cvec *nsc1 = NULL;
|
||||
|
||||
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
|
||||
xpath0="/";
|
||||
/* Create namespace context for xpath from <filter>
|
||||
* The set of namespace declarations are those in scope on the
|
||||
* <filter> element.
|
||||
*/
|
||||
else
|
||||
if (xml_nsctx_node(xfilter, &nsc) < 0)
|
||||
goto done;
|
||||
if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
|
||||
goto done;
|
||||
if (nsc)
|
||||
xml_nsctx_free(nsc);
|
||||
nsc = nsc1;
|
||||
}
|
||||
/* Note xret can be pruned by nacm below (and change name),
|
||||
* so zero-copy cant be used
|
||||
* Also, must use external namespace context here due to <filter stmt
|
||||
|
|
@ -446,25 +385,114 @@ from_client_get_config(clicon_handle h,
|
|||
else{
|
||||
if (xml_name_set(xret, "data") < 0)
|
||||
goto done;
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xpath)
|
||||
free(xpath);
|
||||
if (xnacm)
|
||||
xml_free(xnacm);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
if (xnacm)
|
||||
xml_free(xnacm);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Retrieve all or part of a specified configuration.
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xe Request: <rpc><xn></rpc>
|
||||
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
|
||||
* @param[in] arg client-entry
|
||||
* @param[in] regarg User argument given at rpc_callback_register()
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see from_client_get
|
||||
*/
|
||||
static int
|
||||
from_client_get_config(clicon_handle h,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
int retval = -1;
|
||||
char *db;
|
||||
cxobj *xfilter;
|
||||
char *xpath = NULL;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
int ret;
|
||||
char *username;
|
||||
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
|
||||
yang_stmt *yspec;
|
||||
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
|
||||
char *attr;
|
||||
char *xpath0;
|
||||
cvec *nsc1 = NULL;
|
||||
|
||||
username = clicon_username_get(h);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec9");
|
||||
goto done;
|
||||
}
|
||||
if ((db = netconf_db_find(xe, "source")) == NULL){
|
||||
clicon_err(OE_XML, 0, "db not found");
|
||||
goto done;
|
||||
}
|
||||
if (xmldb_validate_db(db) < 0){
|
||||
if ((cbx = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbx, "No such database: %s", db);
|
||||
if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((xfilter = xml_find(xe, "filter")) != NULL){
|
||||
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
|
||||
xpath0="/";
|
||||
/* Create namespace context for xpath from <filter>
|
||||
* The set of namespace declarations are those in scope on the
|
||||
* <filter> element.
|
||||
*/
|
||||
else
|
||||
if (xml_nsctx_node(xfilter, &nsc) < 0)
|
||||
goto done;
|
||||
if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
|
||||
goto done;
|
||||
if (nsc)
|
||||
xml_nsctx_free(nsc);
|
||||
nsc = nsc1;
|
||||
}
|
||||
/* Clixon extensions: depth */
|
||||
if ((attr = xml_find_value(xe, "depth")) != NULL){
|
||||
char *reason = NULL;
|
||||
if ((ret = parse_int32(attr, &depth, &reason)) < 0){
|
||||
clicon_err(OE_XML, errno, "parse_int32");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){
|
||||
if (netconf_bad_attribute(cbret, "application",
|
||||
"<bad-attribute>depth</bad-attribute>", "Unrecognized value of depth attribute") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
if ((ret = client_config_only(h, nsc, yspec, db, xpath, username, -1, cbret)) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xpath)
|
||||
free(xpath);
|
||||
if (nsc)
|
||||
xml_nsctx_free(nsc);
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
@ -493,7 +521,6 @@ from_client_edit_config(clicon_handle h,
|
|||
cxobj *xc;
|
||||
cxobj *x;
|
||||
enum operation_type operation = OP_MERGE;
|
||||
|
||||
int non_config = 0;
|
||||
yang_stmt *yspec;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
|
|
@ -885,20 +912,26 @@ from_client_get(clicon_handle h,
|
|||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xfilter;
|
||||
char *xpath = NULL;
|
||||
cxobj *xret = NULL;
|
||||
int ret;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
cxobj *xnacm = NULL;
|
||||
char *username;
|
||||
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
|
||||
char *attr;
|
||||
int retval = -1;
|
||||
cxobj *xfilter;
|
||||
char *xpath = NULL;
|
||||
cxobj *xret = NULL;
|
||||
int ret;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
cxobj *xnacm = NULL;
|
||||
char *username;
|
||||
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
|
||||
char *attr;
|
||||
netconf_content content = CONTENT_ALL;
|
||||
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
|
||||
yang_stmt *yspec;
|
||||
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
|
||||
yang_stmt *yspec;
|
||||
int i;
|
||||
#ifdef VALIDATE_STATE_XML
|
||||
cxobj *xerr = NULL;
|
||||
cxobj *xr;
|
||||
cxobj *xb;
|
||||
#endif
|
||||
|
||||
username = clicon_username_get(h);
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
|
|
@ -923,10 +956,10 @@ from_client_get(clicon_handle h,
|
|||
xml_nsctx_free(nsc);
|
||||
nsc = nsc1;
|
||||
}
|
||||
/* Clixon extensions: depth and content */
|
||||
/* Clixon extensions: content */
|
||||
if ((attr = xml_find_value(xe, "content")) != NULL)
|
||||
content = netconf_content_str2int(attr);
|
||||
|
||||
/* Clixon extensions: depth */
|
||||
if ((attr = xml_find_value(xe, "depth")) != NULL){
|
||||
char *reason = NULL;
|
||||
if ((ret = parse_int32(attr, &depth, &reason)) < 0){
|
||||
|
|
@ -940,29 +973,105 @@ from_client_get(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
if (content != CONTENT_NONCONFIG){
|
||||
/* Get config
|
||||
* Note xret can be pruned by nacm below and change name and
|
||||
* metrged with state data, so zero-copy cant be used
|
||||
* Also, must use external namespace context here due to <filter stmt
|
||||
*/
|
||||
if (xmldb_get0(h, "running", nsc, xpath, 1, &xret, NULL) < 0) {
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
if (content != CONTENT_CONFIG){
|
||||
/* Get state data from plugins as defined by plugin_statedata(), if any */
|
||||
clicon_err_reset();
|
||||
if ((ret = client_statedata(h, xpath, nsc, &xret)) < 0)
|
||||
if (content == CONTENT_CONFIG){ /* config only, no state */
|
||||
if (client_config_only(h, nsc, yspec, "running", xpath, username, depth, cbret) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Error from callback (error in xret) */
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
goto ok;
|
||||
}
|
||||
/* If not only-state, then read running config
|
||||
* Note xret can be pruned by nacm below and change name and
|
||||
* metrged with state data, so zero-copy cant be used
|
||||
* Also, must use external namespace context here due to <filter stmt
|
||||
*/
|
||||
if (xmldb_get0(h, "running", nsc, NULL, 1, &xret, NULL) < 0) {
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* If not only config,
|
||||
* get state data from plugins as defined by plugin_statedata(), if any
|
||||
*/
|
||||
clicon_err_reset();
|
||||
if ((ret = client_statedata(h, xpath, nsc, content, &xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Error from callback (error in xret) */
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
#ifdef VALIDATE_STATE_XML
|
||||
/* Check XML by validating it. return internal error with error cause
|
||||
* Primarily intended for user-supplied state-data.
|
||||
* The whole config tree must be present in case the state data references config data
|
||||
*/
|
||||
if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
#if 1
|
||||
if (debug){
|
||||
cbuf *ccc=cbuf_new();
|
||||
if (clicon_xml2cbuf(ccc, xret, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s FAIL: %s", __FUNCTION__, cbuf_get(ccc));
|
||||
cbuf_free(ccc);
|
||||
}
|
||||
#endif
|
||||
if ((xr = xpath_first(xerr, NULL, "//error-tag")) != NULL &&
|
||||
(xb = xml_body_get(xr))){
|
||||
if (xml_value_set(xb, "operation-failed") < 0)
|
||||
goto done;
|
||||
}
|
||||
if ((xr = xpath_first(xerr, NULL, "//error-message")) != NULL &&
|
||||
(xb = xml_body_get(xr))){
|
||||
if (xml_value_append(xb, " Internal error, state callback returned invalid XML") < 0)
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
#endif /* VALIDATE_STATE_XML */
|
||||
if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
|
||||
/* Keep state data only, remove everything that is not config. Note that state data
|
||||
* may be a sub-part in a config tree, we need to traverse to find all
|
||||
*/
|
||||
if (xml_apply(xret, CX_ELMNT, xml_non_config_data, NULL) < 0)
|
||||
goto done;
|
||||
if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Code complex to filter out anything that is outside of xpath
|
||||
* Actually this is a safety catch, should really be done in plugins
|
||||
* and modules_state functions.
|
||||
*/
|
||||
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
/* If vectors are specified then mark the nodes found and
|
||||
* then filter out everything else,
|
||||
* otherwise return complete tree.
|
||||
*/
|
||||
if (xvec != NULL){
|
||||
for (i=0; i<xlen; i++)
|
||||
xml_flag_set(xvec[i], XML_FLAG_MARK);
|
||||
}
|
||||
if (xvec){
|
||||
free(xvec);
|
||||
xvec = NULL;
|
||||
}
|
||||
|
||||
/* Remove everything that is not marked */
|
||||
if (!xml_flag(xret, XML_FLAG_MARK))
|
||||
if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
/* reset flag */
|
||||
if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||
goto done;
|
||||
|
||||
/* Pre-NACM access step */
|
||||
if ((ret = nacm_access_pre(h, username, NACM_DATA, &xnacm)) < 0)
|
||||
goto done;
|
||||
|
|
@ -988,6 +1097,10 @@ from_client_get(clicon_handle h,
|
|||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
#ifdef VALIDATE_STATE_XML
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
#endif
|
||||
if (xpath)
|
||||
free(xpath);
|
||||
if (xnacm)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue