Creator attribute changes: added as xmldb metadata

clixon-config.yang: New revision and Added `CLICON_NETCONF_CREATOR_ATTR` option
clixon-lib.yang: Added creator meta
Changed return value of xml_add_attr
This commit is contained in:
Olof hagsand 2023-11-28 13:35:17 +01:00
parent be3001acf5
commit bbcb4a7b03
20 changed files with 474 additions and 93 deletions

View file

@ -55,9 +55,11 @@ Users may have to change how they access the system
### C/CLI-API changes on existing features
Developers may need to change their code
* Changed return value of `xml_add_attr` from 0/-1 to xa/NULL
* You need to change eg `if (xml_add_attr < 0)` to if (xml_add_attr == NULL)`
* Changed signature of `clicon_netconf_error()` and `netconf_err2cb()`
* You need to add the clixon handle as first parameter:
* `clicon_netconf_error(...)` --> `clicon_netconf_error(h, ...)`
* `clicon_netconf_error(...)` --> `clixon_netconf_error(h, ...)`
* `netconf_err2cb(...)` --> `netconf_err2cb(h, ...)`
* Changed function name for `clicon_debug` functions. You need to rename as follows:
* clicon_debug() -> clixon_debug()
@ -71,8 +73,11 @@ Developers may need to change their code
* New feature: [Customized NETCONF error message](https://github.com/clicon/clixon/issues/454)
* Added new callback `.ca_errmsg`
* See https://clixon-docs.readthedocs.io/en/latest/errors.html#customized-errors for more info
* New `clixon-config@2023-11-01.yang` revision
* Added `CLICON_NETCONF_CREATOR_ATTR` option
* New `clixon-lib@2023-11-01.yang` revision
* Added ignore-compare extension
* Added creator meta configuration
### Corrected Bugs

View file

@ -798,7 +798,7 @@ get_list_pagination(clicon_handle h,
goto done;
}
cprintf(cba, "%u", remaining);
if (xml_add_attr(x1, "remaining", cbuf_get(cba), "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0)
if (xml_add_attr(x1, "remaining", cbuf_get(cba), "cp", "http://clicon.org/clixon-netconf-list-pagination") == NULL)
goto done;
if (cba)
cbuf_free(cba);

View file

@ -473,7 +473,7 @@ cli_dbxml(clicon_handle h,
goto done;
}
}
if (xml_add_attr(xbot, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) < 0)
if (xml_add_attr(xbot, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL)
goto done;
/* Add body last in case of leaf */
if (cvec_len(cvv) > 1 &&

View file

@ -712,7 +712,7 @@ netconf_rpc_dispatch(clicon_handle h,
* It may even be wrong if something else is done with the incoming message?
*/
if ((username = clicon_username_get(h)) != NULL){
if (xml_add_attr(xn, "username", username, CLIXON_LIB_PREFIX, CLIXON_LIB_NS) < 0)
if (xml_add_attr(xn, "username", username, CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL)
goto done;
}
/* Many of these calls are now calling generic clicon_rpc_netconf_xml

View file

@ -427,7 +427,7 @@ restconf_insert_attributes(cxobj *xdata,
/* First add xmlns:yang attribute */
if (xmlns_set(xdata, "yang", YANG_XML_NAMESPACE) < 0)
goto done;
if (xml_add_attr(xdata, "insert", instr, "yang", NULL) < 0)
if (xml_add_attr(xdata, "insert", instr, "yang", NULL) == NULL)
goto done;
}
if ((pstr = cvec_find_str(qvec, "point")) != NULL){
@ -467,7 +467,7 @@ restconf_insert_attributes(cxobj *xdata,
p++;
cprintf(cb, "%s", p);
}
if (xml_add_attr(xdata, attrname, cbuf_get(cb), "yang", NULL) < 0)
if (xml_add_attr(xdata, attrname, cbuf_get(cb), "yang", NULL) == NULL)
goto done;
}
/* Add prefix/namespaces used in attributes */

View file

@ -390,11 +390,11 @@ api_data_write(clicon_handle h,
/* Add operation create as attribute. If that fails with Conflict, then
* try "replace" (see comment in function header)
*/
if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) < 0)
if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL)
goto done;
if (xml_add_attr(xdata, "objectcreate",
plain_patch?"false":"true",
CLIXON_LIB_PREFIX, CLIXON_LIB_NS) < 0)
CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL)
goto done;
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data

View file

@ -235,6 +235,7 @@ int xml_creator_add(cxobj *xn, char *name);
int xml_creator_rm(cxobj *xn, char *name);
int xml_creator_find(cxobj *xn, char *name);
size_t xml_creator_len(cxobj *xn);
cvec *xml_creator_get(cxobj *xn);
int xml_creator_copy_one(cxobj *x0, cxobj *x1);
int xml_creator_copy_all(cxobj *x0, cxobj *x1);
int xml_creator_print(FILE *f, cxobj *xn);
@ -304,7 +305,7 @@ cxobj *xml_root(cxobj *xn);
int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op);
int xml_attr_insert2val(char *instr, enum insert_type *ins);
int xml_add_attr(cxobj *xn, char *name, char *value, char *prefix, char *ns);
cxobj *xml_add_attr(cxobj *xn, char *name, char *value, char *prefix, char *ns);
int clicon_log_xml(int level, cxobj *x, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
int clixon_debug_xml(int dbglevel, cxobj *x, const char *format, ...) __attribute__ ((format (printf, 3, 4)));

View file

@ -442,6 +442,56 @@ disable_nacm_on_empty(cxobj *xt,
return retval;
}
/*! Read creator data from meta-data and add to xml internal annotation
*
* Iterate creator meta-data using xpaths and create internal creator annotations
* @param[in] h Clixon handle
* @param[in] xt XML top node
* @param[in] xn XML creators node
* On the form:
* <creator>
* <name>testA[name='foo']</name>
* <xpath>...</xpath>
* ...
* </creator>
* @see xml_creator_metadata_write
*/
static int
xml_creator_metadata_read(clicon_handle h,
cxobj *xn)
{
int retval = -1;
cxobj *xt;
cxobj *xc;
cxobj *xp;
cxobj *x;
char *name;
char *xpath;
xt = xml_root(xn);
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xc), "creator"))
continue;
if ((name = xml_find_body(xc, "name")) == NULL)
continue;
xp = NULL;
while ((xp = xml_child_each(xc, xp, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xp), "path"))
continue;
if ((xpath = xml_body(xp)) == NULL)
continue;
if ((x = xpath_first(xt, NULL, "%s", xpath)) == NULL)
continue;
if (xml_creator_add(x, name) < 0)
goto done;
}
}
retval = 0;
done:
return retval;
}
/*! Common read function that reads an XML tree from file
*
* @param[in] th Datastore text handle
@ -543,7 +593,19 @@ xmldb_readfile(clicon_handle h,
xml_flag_set(x0, XML_FLAG_TOP);
if (xml_child_nr(x0) == 0 && de)
de->de_empty = 1;
/* Check if creator attributes are supported*/
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
if ((x = xpath_first(x0, NULL, "creators")) != NULL){
ns = NULL;
if (xml2ns(x, NULL, &ns) < 0)
goto done;
if (strcmp(ns, CLIXON_LIB_NS) == 0){
if (xml_creator_metadata_read(h, x) < 0)
goto done;
xml_purge(x);
}
}
}
/* Check if we support modstate */
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msdiff = modstate_diff_new()) == NULL)

View file

@ -89,7 +89,7 @@
* An attribute may have a prefix(or NULL). The routine finds the associated
* xmlns binding to find the namespace: <namespace>:<name>.
* If such an attribute is not found, failure is returned with cbret set,
* If such an attribute its found, its string value is returned.
* If such an attribute is found, its string value is returned and removed from XML
* @param[in] x XML node (where to look for attribute)
* @param[in] name Attribute name
* @param[in] ns (Expected) Namespace of attribute
@ -98,6 +98,7 @@
* @retval 1 OK
* @retval 0 Failed (cbret set)
* @retval -1 Error
* @note as a side.effect the attribute is removed
*/
static int
attr_ns_value(cxobj *x,
@ -142,6 +143,70 @@ attr_ns_value(cxobj *x,
goto done;
}
/*! Add creator data to metadata xml object on the form name:xpath*
*
* Callback function type for xml_apply
* @param[in] x XML node
* @param[in] arg General-purpose argument
* @retval -1 Error, aborted at first error encounter, return -1 to end user
* @retval 0 OK, continue
* @retval 1 Abort, dont continue with others, return 1 to end user
* @retval 2 Locally abort this subtree, continue with others
* On the form:
* <creator>
* <name>testA[name='foo']</name>
* <xpath>...</xpath>
* ...
* </creator>
* @see xml_creator_metadata_read
*/
static int
xml_creator_metadata_write(cxobj *x,
void *arg)
{
int retval = -1;
cxobj *xmeta = (cxobj*)arg;
cxobj *xmc;
cvec *cvv;
cg_var *cv;
char *val;
cvec *nsc = NULL;
char *xpath = NULL;
if ((cvv = xml_creator_get(x)) == NULL){
retval = 0;
goto done;
}
/* Note this requires x to have YANG spec */
if (xml2xpath(x, nsc, 0, 0, &xpath) < 0)
goto done;
cv = NULL;
while ((cv = cvec_each(cvv, cv)) != NULL){
val = cv_name_get(cv);
/* Find existing entry where name is val */
xmc = NULL;
while ((xmc = xml_child_each(xmeta, xmc, CX_ELMNT)) != NULL){
if (strcmp(xml_find_body(xmc, "name"), val) == 0)
break;
}
if (xmc != NULL){
if (clixon_xml_parse_va(YB_NONE, NULL, &xmc, NULL, "<path>%s</path>", xpath) < 0)
goto done;
}
else {
if (clixon_xml_parse_va(YB_NONE, NULL, &xmeta, NULL,
"<creator><name>%s</name><path>%s</path></creator>",
val, xpath) < 0)
goto done;
}
}
retval = 2;
done:
if (xpath)
free(xpath);
return retval;
}
/*! When new body is added, some needs type lookup is made and namespace checked
*
* This includes identityrefs, paths
@ -209,11 +274,11 @@ check_body_namespace(cxobj *x0,
goto done;
/* Create xmlns attribute to x0 XXX same code ^*/
if (prefix){
if (xml_add_attr(x, prefix, ns0, "xmlns", NULL) < 0)
if (xml_add_attr(x, prefix, ns0, "xmlns", NULL) == NULL)
goto done;
}
else
if (xml_add_attr(x, "xmlns", ns0, NULL, NULL) < 0)
if (xml_add_attr(x, "xmlns", ns0, NULL, NULL) == NULL)
goto done;
xml_sort(x); /* Ensure attr is first / XXX xml_insert? */
}
@ -231,7 +296,7 @@ check_body_namespace(cxobj *x0,
;
}
else{ /* Add it according to the kludge,... */
if (xml_add_attr(x0, prefix, ns0, "xmlns", NULL) < 0)
if (xml_add_attr(x0, prefix, ns0, "xmlns", NULL) == NULL)
goto done;
}
}
@ -513,7 +578,6 @@ text_modify(clicon_handle h,
goto done;
if (ret == 0)
goto fail;
if (createstr != NULL &&
(op == OP_REPLACE || op == OP_MERGE || op == OP_CREATE)){
if (x0 == NULL || xml_defaults_nopresence(x0, 0)){ /* does not exist or is default */
@ -532,11 +596,13 @@ text_modify(clicon_handle h,
clicon_data_set(h, "objectexisted", "true");
}
}
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
/* Special clixon-lib attribute for keeping track of creator of objects */
if ((ret = attr_ns_value(x1, "creator", CLIXON_LIB_NS, cbret, &creator)) < 0)
goto done;
if (ret == 0)
goto fail;
}
x1name = xml_name(x1);
if (yang_keyword_get(y0) == Y_LEAF_LIST ||
@ -798,12 +864,13 @@ text_modify(clicon_handle h,
}
/* XXX: Note, if there is an error in adding the object later, the
* original object is not reverted.
* XXX: Here creator attributes in x0 are destroyed
*/
if (x0){
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
/* Recursively copy creator attributes from existing tree */
if (xml_creator_copy_all(x0, x1) < 0)
goto done;
}
xml_purge(x0);
x0 = NULL;
}
@ -854,8 +921,10 @@ text_modify(clicon_handle h,
#ifdef XML_PARENT_CANDIDATE
xml_parent_candidate_set(x0, x0p);
#endif
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
if (xml_creator_copy_one(x1, x0) < 0)
goto done;
}
changed++;
/* Get namespace from x1
* Check if namespace exists in x0 parent
@ -1229,6 +1298,7 @@ xmldb_put(clicon_handle h,
int firsttime = 0;
int pretty;
cxobj *xerr = NULL;
cxobj *xmeta = NULL;
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
@ -1262,6 +1332,16 @@ xmldb_put(clicon_handle h,
xml_name(x0), DATASTORE_TOP_SYMBOL);
goto done;
}
/* XXX Ad-hoc:
* In yang mounts yang specs may not be available
* in initial parsing, but may be at a later stage.
* But it should be possible to find a more precise triggering
*/
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
if ((ret = xml_bind_yang(h, x0, YB_MODULE, yspec, NULL)) < 0)
goto done;
}
/* Here x0 looks like: <config>...</config> */
#if 0 /* debug */
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
@ -1301,7 +1381,6 @@ xmldb_put(clicon_handle h,
if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif
/* Write back to datastore cache if first time */
if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
db_elmnt de0 = {0,};
@ -1336,6 +1415,19 @@ xmldb_put(clicon_handle h,
goto done;
}
pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY");
/* Add creator attribute */
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
if ((xmeta = xml_new("creators", x0, CX_ELMNT)) == NULL)
goto done;
if (xmlns_set(xmeta, NULL, CLIXON_LIB_NS) < 0)
goto done;
if (xml_apply(x0, CX_ELMNT, xml_creator_metadata_write, xmeta) < 0)
goto done;
if (xml_child_nr_type(xmeta, CX_ELMNT) == 0){
xml_purge(xmeta);
xmeta = NULL;
}
}
if (strcmp(format,"json")==0){
if (clixon_json2file(f, x0, pretty, fprintf, 0, 0) < 0)
goto done;
@ -1346,6 +1438,10 @@ xmldb_put(clicon_handle h,
*/
if (xmodst && xml_purge(xmodst) < 0)
goto done;
if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR") && xmeta){
if (xml_purge(xmeta) < 0)
goto done;
}
retval = 1;
done:
if (f != NULL)

View file

@ -348,7 +348,7 @@ json2xml_decode_identityref(cxobj *x,
if (prefix2 == NULL)
prefix2 = yang_find_myprefix(ymod);
/* Add "xmlns:prefix2=namespace" */
if (xml_add_attr(x, prefix2, ns, "xmlns", NULL) < 0)
if (xml_add_attr(x, prefix2, ns, "xmlns", NULL) == NULL)
goto done;
}

View file

@ -178,7 +178,7 @@ netconf_invalid_value_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -302,7 +302,7 @@ netconf_missing_attribute_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -418,7 +418,7 @@ netconf_bad_attribute_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -514,7 +514,7 @@ netconf_common_xml(cxobj **xret,
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
@ -789,7 +789,7 @@ netconf_access_denied_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1034,7 +1034,7 @@ netconf_data_missing_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1093,7 +1093,7 @@ netconf_missing_choice_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1167,7 +1167,7 @@ netconf_operation_not_supported_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1285,7 +1285,7 @@ netconf_operation_failed_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1374,7 +1374,7 @@ netconf_malformed_message_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1460,7 +1460,7 @@ netconf_data_not_unique_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@ -1531,7 +1531,7 @@ netconf_minmax_elements_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;

View file

@ -185,7 +185,8 @@ struct xml{
yang_stmt *x_spec; /* Pointer to specification, eg yang,
by reference, dont free */
cg_var *x_cv; /* Cached value as cligen variable (set by xml_cmp) */
cvec *x_creators; /* Support clixon-lib creator annotation */
cvec *x_creators; /* Support clixon-lib creator annotation.
Only if CLICON_NETCONF_CREATOR_ATTR = true */
#ifdef XML_EXPLICIT_INDEX
struct search_index *x_search_index; /* explicit search index vectors */
#endif
@ -653,7 +654,7 @@ xml_creator_add(cxobj *xn,
cg_var *cv;
if (!is_element(xn))
return 0;
goto ok;
if (xn->x_creators == NULL){
if ((xn->x_creators = cvec_new(0)) == NULL){
clicon_err(OE_XML, errno, "cvec_new");
@ -662,6 +663,7 @@ xml_creator_add(cxobj *xn,
}
if ((cv = cvec_find(xn->x_creators, name)) == NULL)
cvec_add_string(xn->x_creators, name, NULL);
ok:
retval = 0;
done:
return retval;
@ -723,6 +725,16 @@ xml_creator_len(cxobj *xn)
return 0;
}
/*! Get all creator attributes
*/
cvec*
xml_creator_get(cxobj *xn)
{
if (!is_element(xn))
return 0;
return xn->x_creators;
}
/*! Copy creator info from x0 to x1 single object
*
* @param[in] x0 Source XML node
@ -830,7 +842,7 @@ creator_print_fn(cxobj *x,
return 2; /* Locally abort this subtree, continue with others */
}
/*! Print XML and creator tags where they exists, for debugging
/*! Print XML and creator tags where they exists recursively, for debugging
*
* @param[in] xn XML tree
* @retval see xml_apply
@ -1355,7 +1367,7 @@ clixon_child_xvec_append(cxobj *xn,
* xml_free(x);
* @endcode
* @note Differentiates between body/attribute vs element to reduce mem allocation
* @see xml_sort_insert
* @see xml_insert
*/
cxobj *
xml_new(char *name,
@ -2649,18 +2661,17 @@ xml_attr_insert2val(char *instr,
* @param[in] value Attribute value
* @param[in] prefix Attribute prefix, or NULL
* @param[in] namespace Attribute prefix, if NULL do not add namespace mapping
* @retval 0 OK
* @retval -1 Error
* @retval xa Created attribute object
* @retval NULL Error
*/
int
cxobj *
xml_add_attr(cxobj *xn,
char *name,
char *value,
char *prefix,
char *namespace)
{
int retval = -1;
cxobj *xa;
cxobj *xa = NULL;
char *ns = NULL;
if ((xa = xml_new(name, xn, CX_ATTR)) == NULL)
@ -2675,9 +2686,16 @@ xml_add_attr(cxobj *xn,
if (ns == NULL && xmlns_set(xn, prefix, namespace) < 0)
goto done;
}
retval = 0;
if (xml_sort(xn) < 0)
goto done;
ret:
return xa;
done:
return retval;
if (xa){
xml_free(xa);
xa = NULL;
}
goto ret;
}
/*! Specialization of clicon_log with xml tree

View file

@ -227,8 +227,13 @@ populate_self_parent(cxobj *xt,
}
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) != 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Namespace mismatch: %s in XML does not match %s in yang", ns, nsy);
if (xerr &&
netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
netconf_bad_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}

View file

@ -615,7 +615,7 @@ xml_add_default_tag(cxobj *x,
if (xml_flag(x, flags)) {
/* set default attribute */
if (xml_add_attr(x, "default", "true", IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, NULL) < 0)
if (xml_add_attr(x, "default", "true", IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, NULL) == NULL)
goto done;
}
retval = 0;

View file

@ -358,12 +358,14 @@ xml_parse_attr(clixon_xml_yacc *xy,
int retval = -1;
cxobj *xa = NULL;
/* XXX: here duplicates of same attributes are removed
* This is probably not according standard?
*/
if ((xa = xml_find_type(xy->xy_xelement, prefix, name, CX_ATTR)) == NULL){
if ((xa = xml_new(name, xy->xy_xelement, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, prefix) < 0)
goto done;
}
if (xml_value_set(xa, attval) < 0)
goto done;
@ -484,12 +486,14 @@ pi : BQMARK NAME EQMARK {_PARSE_DEBUG("pi -> <? NAME ?>"); free($2); }
;
attrs : attrs attr
|
attrs : attrs attr { _PARSE_DEBUG("attrs -> attrs attr"); }
| { _PARSE_DEBUG("attrs ->"); }
;
attr : NAME '=' attvalue { if (xml_parse_attr(_XY, NULL, $1, $3) < 0) YYABORT; }
| NAME ':' NAME '=' attvalue { if (xml_parse_attr(_XY, $1, $3, $5) < 0) YYABORT; }
attr : NAME '=' attvalue { if (xml_parse_attr(_XY, NULL, $1, $3) < 0) YYABORT;
_PARSE_DEBUG("attr -> NAME = attvalue"); }
| NAME ':' NAME '=' attvalue { if (xml_parse_attr(_XY, $1, $3, $5) < 0) YYABORT;
_PARSE_DEBUG("attr -> NAME : NAME = attvalue"); }
;
attvalue : '\"' STRING '\"' { $$=$2; /* $2 must be consumed */}

View file

@ -1057,7 +1057,7 @@ xml_insert2(cxobj *xp,
/*! Insert xc as child to xp in sorted place. Remove xc from previous parent.
*
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] x Child xml node to insert under xp
* @param[in] xi Child xml node to insert under xp
* @param[in] ins Insert operation (if ordered-by user)
* @param[in] key_val Key if x is LIST and ins is before/after, val if LEAF_LIST
* @param[in] nsc_key Network namespace for key

139
test/test_datastore_creator.sh Executable file
View file

@ -0,0 +1,139 @@
#!/usr/bin/env bash
# test for data creator attribute: add same object from sessions s1 and s2
# Restart and ensure attributes remain
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
cfg=$dir/conf.xml
fyang=$dir/clixon-example.yang
: ${clixon_util_xpath:=clixon_util_xpath}
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>$clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/run/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/run/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_NETCONF_CREATOR_ATTR>true</CLICON_NETCONF_CREATOR_ATTR>
</clixon-config>
EOF
cat <<EOF > $fyang
module clixon-example{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
container table {
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
}
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend 1"
wait_backend
conf="-d candidate -b $dir -y $fyang"
new "s1 add x"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><default-operation>none</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter nc:operation=\"create\" xmlns:nc=\"${BASENS}\" cl:creator=\"s1\" xmlns:cl=\"http://clicon.org/lib\"><name>x</name><value>foo</value></parameter></table></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "datastore get"
expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
new "rpc get-config"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name><value>foo</value></parameter></table>" ""
# duplicate
new "s1 merge x"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><default-operation>none</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter nc:operation=\"merge\" xmlns:nc=\"${BASENS}\" cl:creator=\"s1\" xmlns:cl=\"http://clicon.org/lib\"><name>x</name><value>foo</value></parameter></table></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "datastore get"
expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
# New service
new "s2 merge x"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><default-operation>none</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter nc:operation=\"merge\" xmlns:nc=\"${BASENS}\" cl:creator=\"s2\" xmlns:cl=\"http://clicon.org/lib\"><name>x</name><value>foo</value></parameter></table></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "datastore get"
expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path></creator><creator><name>s2</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
# New entry
new "s1 create y=bar"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><default-operation>none</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter nc:operation=\"create\" xmlns:nc=\"${BASENS}\" cl:creator=\"s1\" xmlns:cl=\"http://clicon.org/lib\"><name>y</name><value>bar</value></parameter></table></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "datastore get"
expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path><path>/table/parameter\[name=\"y\"\]</path></creator><creator><name>s2</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
# To running
new "commit to running"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><commit/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "datastore get running"
expectpart "$(sudo $clixon_util_xpath -f $dir/running_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path><path>/table/parameter\[name=\"y\"\]</path></creator><creator><name>s2</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
new "rpc get-config"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><running/></source></get-config></rpc>" "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name><value>foo</value></parameter><parameter><name>y</name><value>bar</value></parameter></table>" ""
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
fi
if [ $BE -ne 0 ]; then
new "start backend -s running -f $cfg"
start_backend -s running -f $cfg
fi
new "wait backend 2"
wait_backend
new "datastore get running"
expectpart "$(sudo $clixon_util_xpath -f $dir/running_db -p /config/creators)" 0 "<creators xmlns=\"http://clicon.org/lib\"><creator><name>s1</name><path>/table/parameter\[name=\"x\"\]</path><path>/table/parameter\[name=\"y\"\]</path></creator><creator><name>s2</name><path>/table/parameter\[name=\"x\"\]</path></creator></creators>"
new "rpc get-config"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><running/></source></get-config></rpc>" "<table xmlns=\"urn:example:clixon\"><parameter><name>x</name><value>foo</value></parameter><parameter><name>y</name><value>bar</value></parameter></table>" ""
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
fi
rm -rf $dir
new "endtest"
endtest

View file

@ -42,7 +42,7 @@ datarootdir = @datarootdir@
YANG_INSTALLDIR = @YANG_INSTALLDIR@
# Note: mirror these to test/config.sh.in
YANGSPECS = clixon-config@2023-05-01.yang # 6.3
YANGSPECS = clixon-config@2023-11-01.yang # 6.5
YANGSPECS += clixon-lib@2023-11-01.yang # 6.5
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang

View file

@ -9,6 +9,9 @@ module clixon-config {
import clixon-autocli {
prefix autocli;
}
import clixon-lib {
prefix cl;
}
organization
"Clicon / Clixon";
@ -46,6 +49,20 @@ module clixon-config {
***** END LICENSE BLOCK *****";
revision 2023-11-01 {
description
"Added options:
CLICON_CREATOR_ATTR
Released in Clixon 6.5";
}
revision 2023-05-01 {
description
"Added options:
CLICON_CONFIG_EXTEND
CLICON_PLUGIN_DLOPEN_GLOBAL
Moved datastore-format datatype to clixon-lib
Released in Clixon 6.3";
}
revision 2023-03-01 {
description
"Added options:
@ -274,27 +291,6 @@ module clixon-config {
}
}
}
typedef datastore_format{
description
"Datastore format (only xml and json implemented in actual data.";
type enumeration{
enum xml{
description
"Save and load xmldb as XML
More specifically, such a file looks like: <config>...</config> provided
DATASTORE_TOP_SYMBOL is 'config'";
}
enum json{
description "Save and load xmldb as JSON";
}
enum text{
description "'Curly' C-like text format";
}
enum cli{
description "CLI format";
}
}
}
typedef datastore_cache{
description
"XML configuration, ie running/candididate/ datastore cache behaviour.";
@ -461,6 +457,15 @@ module clixon-config {
You can override file setting with -E <dir> command-line option.
Note that due to bootstraping this value is only meaningful in the main config file";
}
leaf CLICON_CONFIG_EXTEND {
type string;
description
"If specified load an application-specific configuration YANG that overrides
this config.
Normally, that YANG imports clixon-config.
This field is a 'bootstrap' field.
";
}
leaf CLICON_YANG_MAIN_FILE {
type string;
description
@ -520,7 +525,13 @@ module clixon-config {
leaf CLICON_YANG_SCHEMA_MOUNT{
type boolean;
description
"YANG schema mount, RFC 8528";
"YANG schema mount, RFC 8528.
When enabled, mount-points as defined by the 'yangmnt:mount-point' extension can
be populated by other YANGs than the root.
This is controlled by the ca_yang_mount plugin callback by returning a assigning a
yanglib module-set section that corresponds to the mounted YANGs.
Also, schema mount statistics is added to state data
Further, autocli syntax is added by definining a tree resolve wrapper";
default false;
}
leaf CLICON_BACKEND_REGEXP {
@ -571,6 +582,18 @@ module clixon-config {
RFC6242 for example.
This only applies to the external NETCONF";
}
leaf CLICON_NETCONF_CREATOR_ATTR {
type boolean;
default false;
description
"If set, clixon will accept the 'creator' attribute as defined by the
creator annotation in clixon-lib.
It can be used when several clients (such as a 'service') can create the same object.
If one such client/service is deleted, the object is deleted only if all services
that created the object are deleted.
The clixon controller uses this feature, but could in principle be used by other
applications.";
}
leaf CLICON_RESTCONF_API_ROOT {
type string;
default "/restconf";
@ -612,7 +635,7 @@ module clixon-config {
edit operation.
Setting this option disables this behaviour, ie the startup configuration is NOT
automatically updated.
If this option is false, the startup is autoamtically updated following the RFC";
If this option is false, the startup is automatically updated following the RFC";
}
leaf CLICON_RESTCONF_USER {
type string;
@ -931,7 +954,7 @@ module clixon-config {
Others are experimental (in Clixon 5.5)";
}
leaf CLICON_XMLDB_FORMAT {
type datastore_format;
type cl:datastore_format;
default xml;
description "XMLDB datastore format.";
}
@ -1005,6 +1028,18 @@ module clixon-config {
as well as the CLIgen callbacks.
See https://clixon-docs.readthedocs.io/en/latest/backend.html#plugin-callback-guidelines";
}
leaf CLICON_PLUGIN_DLOPEN_GLOBAL {
type boolean;
default false;
description
"Local/global flag for dlopen as described in the man page.
This applies to the opening of all clixon plugins (backend/cli/netconf/restconf)
when loading the shared .so file with dlopen.
If false: Symbols defined in this shared object are not made available to resolve
references in subsequently loaded shared objects (default).
If true: The symbols defined by this shared object will be made available for symbol res
olution of subsequently loaded shared objects.";
}
leaf CLICON_YANG_AUGMENT_ACCEPT_BROKEN {
type boolean;
default false;
@ -1094,7 +1129,7 @@ module clixon-config {
restconf GET.
The module state data is on the form:
<yang-library><module-set>...
instead where the modile state is on the form:
instead where the module state is on the form:
<modules-state>...
See also CLICON_XMLDB_MODSTATE where the module state info is used to tag datastores
with module information.";
@ -1131,7 +1166,7 @@ module clixon-config {
default false;
description
"Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
section 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_DISCOVERY_RFC8040 {

View file

@ -71,6 +71,7 @@ module clixon-lib {
revision 2023-11-01 {
description
"Added ignore-compare extension
Added creator meta configuration
Removed obsolete extension autocli-op
Released in 6.5.0";
}
@ -209,6 +210,21 @@ module clixon-lib {
Limitations: only objects that are actually added or deleted.
A sub-object will not be noted";
}
container creators{
config false;
description "Meta-data for creator attribute.";
list creator {
key name;
leaf name {
description "Name of creator / service (instance) name";
type string;
}
leaf-list path {
description "Path to object";
type string;
}
}
}
rpc debug {
description "Set debug level of backend.";
input {