From bbcb4a7b0349de845f54a98fb639252b0345ede5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 28 Nov 2023 13:35:17 +0100 Subject: [PATCH] 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 --- CHANGELOG.md | 7 +- apps/backend/backend_get.c | 2 +- apps/cli/cli_common.c | 2 +- apps/netconf/netconf_rpc.c | 2 +- apps/restconf/restconf_lib.c | 4 +- apps/restconf/restconf_methods.c | 4 +- lib/clixon/clixon_xml.h | 3 +- lib/src/clixon_datastore_read.c | 64 +++++++- lib/src/clixon_datastore_write.c | 130 +++++++++++++--- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 24 +-- lib/src/clixon_xml.c | 48 ++++-- lib/src/clixon_xml_bind.c | 7 +- lib/src/clixon_xml_default.c | 2 +- lib/src/clixon_xml_parse.y | 14 +- lib/src/clixon_xml_sort.c | 2 +- test/test_datastore_creator.sh | 139 ++++++++++++++++++ yang/clixon/Makefile.in | 2 +- ...-01.yang => clixon-config@2023-11-01.yang} | 93 ++++++++---- yang/clixon/clixon-lib@2023-11-01.yang | 16 ++ 20 files changed, 474 insertions(+), 93 deletions(-) create mode 100755 test/test_datastore_creator.sh rename yang/clixon/{clixon-config@2023-03-01.yang => clixon-config@2023-11-01.yang} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2474c40..1c639b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 55291511..39bece86 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -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); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index fd822a04..28a31cfd 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -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 && diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 994df53d..283ab0a3 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -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 diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index c8c5a916..def540b1 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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 */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 1585c950..49e3e35b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -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 diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 67312c6a..71417713 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -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))); diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 5caf898f..fb9d5765 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -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: + * + * testA[name='foo'] + * ... + * ... + * + * @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) diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 85b136c6..ef008562 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -89,7 +89,7 @@ * An attribute may have a prefix(or NULL). The routine finds the associated * xmlns binding to find the namespace: :. * 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: + * + * testA[name='foo'] + * ... + * ... + * + * @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, "%s", xpath) < 0) + goto done; + } + else { + if (clixon_xml_parse_va(YB_NONE, NULL, &xmeta, NULL, + "%s%s", + 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"); } } - /* 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; + 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){ - /* Recursively copy creator attributes from existing tree */ - if (xml_creator_copy_all(x0, x1) < 0) - goto done; + 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 (xml_creator_copy_one(x1, x0) < 0) - goto done; + 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: ... */ #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) diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 5a95dab0..54f87c97 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -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; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index db1584f3..6b36d029 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -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; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 8618a33b..d5e696bd 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -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, @@ -2021,10 +2033,10 @@ xml_find_type_value(cxobj *xt, * @see xml_find_value where a body can be found as well */ cxobj * -xml_find_type(cxobj *xt, - const char *prefix, - const char *name, - enum cxobj_type type) +xml_find_type(cxobj *xt, + const char *prefix, + const char *name, + enum cxobj_type type) { cxobj *x = NULL; int pmatch; /* prefix match */ @@ -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 diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index d943aa96..0c8b97f3 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -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; } diff --git a/lib/src/clixon_xml_default.c b/lib/src/clixon_xml_default.c index 11fdd969..944b5afc 100644 --- a/lib/src/clixon_xml_default.c +++ b/lib/src/clixon_xml_default.c @@ -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; diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 30ff1df1..0cbe564e 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -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 -> "); 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 */} diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 6bac6f5c..45e7bfdb 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -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 diff --git a/test/test_datastore_creator.sh b/test/test_datastore_creator.sh new file mode 100755 index 00000000..53f1caa9 --- /dev/null +++ b/test/test_datastore_creator.sh @@ -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 < $cfg + + $cfg + ${YANG_INSTALLDIR} + $dir + $fyang + $clispec + /usr/local/lib/$APPNAME/cli + example + /usr/local/var/run/$APPNAME.sock + /usr/local/var/run/$APPNAME.pidfile + $dir + true + +EOF + +cat < $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" "nonexfoo
" "" "" + +new "datastore get" +expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1/table/parameter\[name=\"x\"\]" + +new "rpc get-config" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "xfoo
" "" + +# duplicate +new "s1 merge x" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "nonexfoo
" "" "" + +new "datastore get" +expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1/table/parameter\[name=\"x\"\]" + +# New service +new "s2 merge x" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "nonexfoo
" "" "" + +new "datastore get" +expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1/table/parameter\[name=\"x\"\]s2/table/parameter\[name=\"x\"\]" + +# New entry +new "s1 create y=bar" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "noneybar
" "" "" + +new "datastore get" +expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1/table/parameter\[name=\"x\"\]/table/parameter\[name=\"y\"\]s2/table/parameter\[name=\"x\"\]" + +# To running +new "commit to running" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "datastore get running" +expectpart "$(sudo $clixon_util_xpath -f $dir/running_db -p /config/creators)" 0 "s1/table/parameter\[name=\"x\"\]/table/parameter\[name=\"y\"\]s2/table/parameter\[name=\"x\"\]" + +new "rpc get-config" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "xfooybar
" "" + +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 "s1/table/parameter\[name=\"x\"\]/table/parameter\[name=\"y\"\]s2/table/parameter\[name=\"x\"\]" + +new "rpc get-config" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "xfooybar
" "" + +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 diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 6d9987d8..b5615e34 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -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 diff --git a/yang/clixon/clixon-config@2023-03-01.yang b/yang/clixon/clixon-config@2023-11-01.yang similarity index 94% rename from yang/clixon/clixon-config@2023-03-01.yang rename to yang/clixon/clixon-config@2023-11-01.yang index 3851572f..cd72a1fc 100644 --- a/yang/clixon/clixon-config@2023-03-01.yang +++ b/yang/clixon/clixon-config@2023-11-01.yang @@ -8,7 +8,10 @@ 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: @@ -225,7 +242,7 @@ module clixon-config { Renamed CLICON_XMLDB_CACHE to CLICON_DATASTORE_CACHE (changed type) Deleted: CLICON_XMLDB_PLUGIN, CLICON_USE_STARTUP_CONFIG"; } - revision 2019-03-05{ + revision 2019-03-05{ description "Changed URN. Changed top-level symbol to clixon-config. Released in Clixon 3.10"; @@ -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: ... 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 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; @@ -675,7 +698,7 @@ module clixon-config { Both feature clixon-restconf:http-data and restconf/enable-http-data must be enabled for this match to occur."; } - leaf CLICON_HTTP_DATA_ROOT{ + leaf CLICON_HTTP_DATA_ROOT{ if-feature "clrc:http-data"; type string; default "/var/www"; @@ -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: ... - instead where the modile state is on the form: + instead where the module state is on the form: ... 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 { diff --git a/yang/clixon/clixon-lib@2023-11-01.yang b/yang/clixon/clixon-lib@2023-11-01.yang index 620bfedc..f34a6f01 100644 --- a/yang/clixon/clixon-lib@2023-11-01.yang +++ b/yang/clixon/clixon-lib@2023-11-01.yang @@ -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 {