diff --git a/CHANGELOG.md b/CHANGELOG.md index 6353a40e..9d7dc3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,17 @@ ### Minor changes * Added wildcard `*` as a mode to `CLICON_MODE` in clispec files * If you set "CLICON_MODE="*";" in a clispec file it means that syntax will appear in all CLI spec modes. +* State callbacks provided by user are validated. If they are invalid an internal error is returned. +* Fixed multi-namespace for augmented state which was not covered in 4.2.0. + + +### API changes on existing features (you may need to change your code) +* The multi-namespace augment state may rearrange the XML namespace attributes. +* Main example yang changed to incorporate augmented state, new revision is 2019-11-15. ### Corrected Bugs + +* Mandatory variables can no longer be deleted. * [Add missing includes](https://github.com/clicon/clixon/pulls) ## 4.2.0 (October 27 2019) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 8a379c0d..2aee6dca 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -170,7 +170,7 @@ client_get_capabilities(clicon_handle h, int retval = -1; cxobj *xrstate = NULL; /* xml restconf-state node */ cxobj *xcap = NULL; /* xml capabilities node */ - + if ((xrstate = xpath_first(*xret, "restconf-state")) == NULL){ clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); goto done; @@ -321,7 +321,7 @@ client_statedata(clicon_handle h, if (ret == 0) goto fail; /* Code complex to filter out anything that is outside of xpath - * Actually this is a safety catch, should realy be done in plugins + * Actually this is a safety catch, should really be done in plugins * and modules_state functions. */ if (xpath_vec_nsc(*xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 7c45145c..a22e8360 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -118,10 +118,14 @@ generic_validate(clicon_handle h, for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); - if (ys && yang_mandatory(ys) && yang_config(ys)==0){ - if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), "Missing mandatory variable") < 0) - goto done; - goto fail; + if (ys && yang_mandatory(ys) && yang_config(ys)==1){ + yang_stmt *yp =yang_parent_get(ys); + if (yp== NULL || + (yang_keyword_get(yp)!=Y_MODULE && yang_keyword_get(yp)!=Y_SUBMODULE)){ + if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), "May not remove mandatory variable") < 0) + goto done; + goto fail; + } } } /* added entries */ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index a0dc92d3..450fd720 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -111,6 +111,7 @@ clixon_plugin_statedata(clicon_handle h, { int retval = -1; int ret; + cxobj *xerr = NULL; cxobj *x = NULL; clixon_plugin *cp = NULL; plgstatedata_t *fn; /* Plugin statedata fn */ @@ -124,6 +125,31 @@ clixon_plugin_statedata(clicon_handle h, goto fail; /* Dont quit here on user callbacks */ if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; + /* Check XML from state callback by validating it. return internal + * error with error cause + */ + if ((ret = xml_yang_validate_all_top(h, x, &xerr)) < 0) + goto done; + if (ret > 0 && (ret = xml_yang_validate_add(h, x, &xerr)) < 0) + goto done; + if (ret == 0){ + cbuf *cberr = NULL; /* XXX Cumbersome, try to fold into one cb */ + cbuf *cberr2 = NULL; + if (netconf_err2cb(xpath_first(xerr, "rpc-error"), &cberr) < 0) + goto done; + if ((cberr2 = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr2, "Internal error: state callback returned invalid XML: %s", cbuf_get(cberr)); + if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr2))< 0) + goto done; + if (cberr) + cbuf_free(cberr); + if (cberr2) + cbuf_free(cberr2); + goto fail; + } if ((ret = netconf_trymerge(x, yspec, xret)) < 0) goto done; if (ret == 0) @@ -137,6 +163,8 @@ clixon_plugin_statedata(clicon_handle h, done: if (x) xml_free(x); + if (xerr) + xml_free(xerr); return retval; fail: retval = 0; diff --git a/example/hello/README.md b/example/hello/README.md index 0c7c3a0b..3f851677 100644 --- a/example/hello/README.md +++ b/example/hello/README.md @@ -82,7 +82,7 @@ Start restconf daemon Start sending restconf commands (using Curl): ``` - olof@vandal> curl -X POST http://localhost/restconf/data -d '{"clixon-hello:hello":{"world":null}}' + olof@vandal> curl -X POST http://localhost/restconf/data -H "Content-Type: application/yang-data+json" -d '{"clixon-hello:hello":{"world":null}}' olof@vandal> curl -X GET http://localhost/restconf/data { "data": { diff --git a/example/main/Makefile.in b/example/main/Makefile.in index 98596fd8..cc8cbbfb 100644 --- a/example/main/Makefile.in +++ b/example/main/Makefile.in @@ -79,7 +79,7 @@ all: $(PLUGINS) CLISPECS = $(APPNAME)_cli.cli -YANGSPECS = clixon-example@2019-07-23.yang +YANGSPECS = clixon-example@2019-11-05.yang # Backend plugin BE_SRC = $(APPNAME)_backend.c diff --git a/example/main/README.md b/example/main/README.md index 8abd26ee..b5101aca 100644 --- a/example/main/README.md +++ b/example/main/README.md @@ -206,7 +206,7 @@ clixon_netconf -qf /usr/local/etc/example.xml Restconf (assuming nginx started): ``` sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data& -curl -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":"ipv4"}}' +curl -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"ipv4"}}' { "clixon-example:output": { "x": "ipv4", diff --git a/example/main/clixon-example@2019-07-23.yang b/example/main/clixon-example@2019-11-05.yang similarity index 92% rename from example/main/clixon-example@2019-07-23.yang rename to example/main/clixon-example@2019-11-05.yang index da93468f..b0c8e492 100644 --- a/example/main/clixon-example@2019-07-23.yang +++ b/example/main/clixon-example@2019-11-05.yang @@ -2,6 +2,9 @@ module clixon-example { yang-version 1.1; namespace "urn:example:clixon"; prefix ex; + revision 2019-11-05 { + description "Augment interface. Released in Clixon 4.3.0"; + } revision 2019-07-23 { description "Extension e4. Released in Clixon 4.1.0"; } @@ -42,6 +45,18 @@ module clixon-example { type string; } } + augment "/if:interfaces/if:interface" { + container my-status { + config false; + description "For testing augment+state"; + leaf int { + type int32; + } + leaf str { + type string; + } + } + } /* yang extension implemented by the example backend code. */ extension e4 { description diff --git a/example/main/example_backend.c b/example/main/example_backend.c index 25a38514..928c07d2 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -317,30 +317,23 @@ example_statedata(clicon_handle h, * state information. In this case adding dummy interface operation state * to configured interfaces. * Get config according to xpath */ - if (xmldb_get0(h, "running", nsc, xpath, 1, &xt, NULL) < 0) + if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL) goto done; - - if (yang_find_module_by_namespace(yspec, "urn:ietf:params:xml:ns:yang:ietf-interfaces") != NULL){ - /* Here a separate namespace context nsc1 is created. The original nsc - * created by the system cannot be used trivially, since we dont know - * the prefixes, although we could by a complex mechanism find the prefix - * (if it exists) and use that when creating our xpath. - * But it is easier creating a new namespace context nsc1. - */ - if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL) - goto done; - if (xpath_vec_nsc(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0) - goto done; - if (xlen){ - cprintf(cb, ""); - for (i=0; i%sup", name); - } - cprintf(cb, ""); - if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0) - goto done; + if (xmldb_get0(h, "running", nsc1, "/interfaces/interface/name", 1, &xt, NULL) < 0) + goto done; + if (xpath_vec_nsc(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0) + goto done; + if (xlen){ + cprintf(cb, ""); + for (i=0; i%sex:ethup", name); + cprintf(cb, "42foo"); + cprintf(cb, ""); } + cprintf(cb, ""); + if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0) + goto done; } /* State in test_yang.sh , test_restconf.sh and test_order.sh */ if (yang_find_module_by_namespace(yspec, "urn:example:clixon") != NULL){ @@ -356,32 +349,14 @@ example_statedata(clicon_handle h, * (2) event-count is XOR on name, so is not 42 and 4 */ if (yang_find_module_by_namespace(yspec, "urn:example:events") != NULL){ - if ((nsc2 = xml_nsctx_init(NULL, "urn:example:events")) == NULL) + cbuf_reset(cb); + cprintf(cb, ""); + cprintf(cb, "interface-down90"); + cprintf(cb, "interface-up77"); + cprintf(cb, ""); + if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0) goto done; - if (xvec){ - free(xvec); - xvec = NULL; - } - if (xpath_vec_nsc(xt, nsc2, "/events/event/name", &xvec, &xlen) < 0) - goto done; - if (xlen){ - int j = 0; - int c; - cprintf(cb, ""); - - for (i=0; i%s%d", name, c); - } - cprintf(cb, ""); - if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0) - goto done; - } } - ok: retval = 0; done: diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 56bcf9ef..bf1ffc0a 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -44,7 +44,7 @@ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int *sock0); int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp); -int clicon_rpc_generate_error(char *format, cxobj *xerr); +int clicon_rpc_generate_error(const char *format, cxobj *xerr); int clicon_rpc_get_config(clicon_handle h, char *username, char *db, char *xpath, cvec *nsc, cxobj **xret); int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op, char *xml); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 8a908f03..250acfe5 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -78,6 +78,7 @@ int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, int xml2xpath(cxobj *x, char **xpath); int xml2api_path_1(cxobj *x, cbuf *cb); +int check_namespaces(cxobj *x0, cxobj *x1, cxobj *x1p); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index b6f294da..9daf9b93 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -132,138 +132,6 @@ attr_ns_value(cxobj *x, goto done; } -/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace - * @param[in] x0 Source XML tree - * @param[in] x1 Target XML tree - * 1. Find N=namespace(x0) - * 2. Detect if N is declared in x1 parent - * 3. If yes, assign prefix to x1 - * 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root) - * 5. Add prefix to x1, if any - * 6. Ensure x1 cache is updated - * @note switch use of x0 and x1 compared to datastore text_modify - * @see xml2ns - * XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0) - goto done; - */ -static int -check_namespaces(cxobj *x0, - cxobj *x1, - cxobj *x1p) -{ - int retval = -1; - char *namespace = NULL; - char *prefix0 = NULL;; - char *prefix10 = NULL; /* extra just for malloc problem */ - char *prefix1 = NULL;; - char *prefixb = NULL; /* identityref body prefix */ - cvec *nsc0 = NULL; - cvec *nsc = NULL; - cxobj *xa = NULL; - cxobj *x; - int isroot; - - /* XXX: need to identify root better than hiereustics and strcmp,... */ - isroot = xml_parent(x1p)==NULL && - strcmp(xml_name(x1p), "config") == 0 && - xml_prefix(x1p)==NULL; - - /* 1. Find N=namespace(x0) */ - prefix0 = xml_prefix(x0); - if (xml2ns(x0, prefix0, &namespace) < 0) - goto done; - if (namespace == NULL){ - clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s", - prefix0?prefix0:"NULL"); - goto done; - } - /* 2. Detect if namespace is declared in x1:s parent */ - if (xml2prefix(x1p, namespace, &prefix10) == 1){ - if (prefix10){ - if ((prefix1 = strdup(prefix10)) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - } - else - prefix1 = NULL; - /* 3. If yes, assign prefix to x1 */ - if (prefix1 && xml_prefix_set(x1, prefix1) < 0) - goto done; - /* And copy namespace context from parent to child */ - if ((nsc0 = nscache_get_all(x1p)) != NULL){ - if ((nsc = cvec_dup(nsc0)) == NULL){ - clicon_err(OE_UNIX, errno, "cvec_dup"); - goto done; - } - nscache_replace(x1, nsc); - } - /* Just in case */ - if (nscache_set(x1, prefix1, namespace) < 0) - goto done; - } - else{ - /* 4. If no, create new prefix/namespace binding and assign that to x1p - * use modules own default prefix (some chance for clash) - */ - if (prefix0 == NULL && !isroot){ - assert(xml_spec(x1) != NULL); - prefix0 = yang_find_myprefix(xml_spec(x1)); - } - if (prefix0) - if ((prefix1 = strdup(prefix0)) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - - /* Add binding to x1p. We add to parent due to heurestics, so we dont - * end up in adding it to large number of siblings - */ - if (isroot) - x = x1; - else - x = x1p; - if (nscache_set(x, prefix1, namespace) < 0) - goto done; - if (x == x1p){ - if ((nsc0 = nscache_get_all(x1p)) != NULL) - if ((nsc = cvec_dup(nsc0)) == NULL){ - clicon_err(OE_UNIX, errno, "cvec_dup"); - goto done; - } - /* Copy x1p cache to x1 */ - nscache_replace(x1, nsc); - } - /* Create xmlns attribute to x1p/x1 XXX same code v */ - if (prefix1){ - if ((xa = xml_new(prefix1, x, NULL)) == NULL) - goto done; - if (xml_prefix_set(xa, "xmlns") < 0) - goto done; - } - else{ - if ((xa = xml_new("xmlns", x, NULL)) == NULL) - goto done; - } - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, namespace) < 0) - goto done; - xml_sort(x, NULL); /* Ensure attr is first / XXX xml_insert? */ - - /* 5. Add prefix to x1, if any */ - if (prefix1 && xml_prefix_set(x1, prefix1) < 0) - goto done; - } - /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ - retval = 0; - done: - if (prefixb) - free(prefixb); - if (prefix1) - free(prefix1); - return retval; -} - static int check_identityref(cxobj *x0, cxobj *x1, diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index e8601af4..2f9efaec 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1338,7 +1338,7 @@ netconf_db_find(cxobj *xn, } /*! Generate netconf error msg to cbuf to use in string printout or logs - * @param[in] xerr Netconf error message on the level: + * @param[in] xerr Netconf error message on the level: * @param[out] cberr Translation from netconf err to cbuf. Free with cbuf_free. * @retval 0 OK, with cberr set * @retval -1 Error diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 785b1eb0..146cc52a 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -227,11 +227,11 @@ clicon_rpc_netconf_xml(clicon_handle h, /*! Generate and log clicon error function call from Netconf error message * @param[in] prefix Print this string (if given) before: ": " - * @param[in] xerr Netconf error message on the level: + * @param[in] xerr Netconf error message on the level: */ int -clicon_rpc_generate_error(char *prefix, - cxobj *xerr) +clicon_rpc_generate_error(const char *prefix, + cxobj *xerr) { int retval = -1; cbuf *cb = NULL; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index a1dc2730..e6217e37 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2153,6 +2153,46 @@ xml_tree_prune_flagged(cxobj *xt, return retval; } +/*! Add prefix:namespace pair to xml node, set cache, prefix, etc + */ +static int +add_namespace(cxobj *x1, /* target */ + cxobj *x1p, + char *prefix1, + char *namespace) +{ + int retval = -1; + cxobj *xa = NULL; + + /* Add binding to x1p. We add to parent due to heurestics, so we dont + * end up in adding it to large number of siblings + */ + if (nscache_set(x1, prefix1, namespace) < 0) + goto done; + /* Create xmlns attribute to x1p/x1 XXX same code v */ + if (prefix1){ + if ((xa = xml_new(prefix1, x1, NULL)) == NULL) + goto done; + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + } + else{ + if ((xa = xml_new("xmlns", x1, NULL)) == NULL) + goto done; + } + xml_type_set(xa, CX_ATTR); + if (xml_value_set(xa, namespace) < 0) + goto done; + xml_sort(x1, NULL); /* Ensure attr is first / XXX xml_insert? */ + + /* 5. Add prefix to x1, if any */ + if (prefix1 && xml_prefix_set(x1, prefix1) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! Add default values (if not set) * @param[in] xt XML tree with some node marked * @param[in] arg Ignored @@ -2175,6 +2215,7 @@ xml_default(cxobj *xt, int added=0; char *namespace; char *prefix; + int ret; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ retval = 0; @@ -2195,9 +2236,22 @@ xml_default(cxobj *xt, /* assign right prefix */ if ((namespace = yang_find_mynamespace(y)) != NULL){ prefix = NULL; - if (xml2prefix(xt, namespace, &prefix)) + if ((ret = xml2prefix(xt, namespace, &prefix)) < 0) + goto done; + if (ret){ if (xml_prefix_set(xc, prefix) < 0) goto done; + } + else{ /* namespace does not exist in target, use source prefix */ + char *prefix1 = NULL; + if ((prefix1 = strdup(yang_find_myprefix(y))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + + if (add_namespace(xc, xt, prefix1, namespace) < 0) + goto done; + } } xml_flag_set(xc, XML_FLAG_DEFAULT); @@ -3190,6 +3244,124 @@ xmlns_assign(cxobj *x) return retval; } +/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace + * @param[in] x0 Source XML tree + * @param[in] x1 Target XML tree + * 1. Find N=namespace(x0) + * 2. Detect if N is declared in x1 parent + * 3. If yes, assign prefix to x1 + * 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root) + * 5. Add prefix to x1, if any + * 6. Ensure x1 cache is updated + * @note switch use of x0 and x1 compared to datastore text_modify + * @see xml2ns + * XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0) + goto done; + */ +int +check_namespaces(cxobj *x0, /* source */ + cxobj *x1, /* target */ + cxobj *x1p) +{ + int retval = -1; + char *namespace = NULL; + char *prefix0 = NULL;; + char *prefix1 = NULL; + char *prefixb = NULL; /* identityref body prefix */ + cvec *nsc0 = NULL; + cvec *nsc = NULL; + int isroot; + char *pexist = NULL; + yang_stmt *y; + + /* XXX: need to identify root better than hiereustics and strcmp,... */ + isroot = xml_parent(x1p)==NULL && + (strcmp(xml_name(x1p), "config") == 0 || strcmp(xml_name(x1p), "top") == 0)&& + xml_prefix(x1p)==NULL; + + /* 1. Find N=namespace(x0) */ + prefix0 = xml_prefix(x0); + if (xml2ns(x0, prefix0, &namespace) < 0) + goto done; + if (namespace == NULL){ + clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s", + prefix0?prefix0:"NULL"); + goto done; + } + /* 2a. Detect if namespace is declared in x1 target parent */ + if (xml2prefix(x1p, namespace, &pexist) == 1){ + /* Yes, and it has prefix pexist */ + if (pexist){ + if ((prefix1 = strdup(pexist)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else + prefix1 = NULL; + /* 3. If yes, assign prefix to x1 */ + if (prefix1 && xml_prefix_set(x1, prefix1) < 0) + goto done; + /* And copy namespace context from parent to child */ + if ((nsc0 = nscache_get_all(x1p)) != NULL){ + if ((nsc = cvec_dup(nsc0)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + nscache_replace(x1, nsc); + } + /* Just in case */ + if (nscache_set(x1, prefix1, namespace) < 0) + goto done; + } + else{ /* No, namespace does not exist in x1 _parent_ + * Check if it is exists in x1 itself */ + if (nscache_get_prefix(x1, namespace, &pexist) == 1){ + /* Yes it exists, but is it equal? */ + if ((pexist == NULL && prefix0 == NULL) || + (pexist && prefix0 && + strcmp(pexist, prefix0)==0)){ /* Equal, reuse */ + ; + } + else{ /* namespace exist, but not equal, use existing */ + /* Add prefix to x1, if any */ + if (pexist && xml_prefix_set(x1, pexist) < 0) + goto done; + } + goto ok; /* skip */ + } + else + { /* namespace does not exist in target x1, use source prefix + * use the prefix defined in the module + */ + if (isroot){ + if (prefix0 && (prefix1 = strdup(prefix0)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else{ + y = xml_spec(x0); + if ((prefix1 = strdup(yang_find_myprefix(y))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + } + if (add_namespace(x1, x1p, prefix1, namespace) < 0) + goto done; + } + ok: + /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ + retval = 0; + done: + if (prefixb) + free(prefixb); + if (prefix1) + free(prefix1); + return retval; +} + /*! Merge a base tree x0 with x1 with yang spec y * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL @@ -3201,20 +3373,18 @@ xmlns_assign(cxobj *x) * Assume x0 and x1 are same on entry and that y is the spec */ static int -xml_merge1(cxobj *x0, +xml_merge1(cxobj *x0, /* the target */ yang_stmt *y0, cxobj *x0p, - cxobj *x1, + cxobj *x1, /* the source */ char **reason) { int retval = -1; - char *x1name; char *x1cname; /* child name */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ - cxobj *x0a; /* x0 xmlns attribute */ - cxobj *x1a; /* x1 xmlns attribute */ + char *x1name; char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ @@ -3243,13 +3413,16 @@ xml_merge1(cxobj *x0, if (xml_value_set(x0b, x1bstr) < 0) goto done; } - + if (check_namespaces(x1, x0, x0p) < 0) + goto done; } /* if LEAF|LEAF_LIST */ else { /* eg Y_CONTAINER, Y_LIST */ if (x0==NULL){ - if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) + if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL) goto done; } + if (check_namespaces(x1, x0, x0p) < 0) + goto done; /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { @@ -3277,19 +3450,11 @@ xml_merge1(cxobj *x0, goto done; if (*reason != NULL) goto ok; - } + } /* while */ + if (xml_parent(x0) == NULL && + xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0) + goto done; } /* else Y_CONTAINER */ - assert(x0); - /* Copy xmlns attributes (if it does not already exist) */ - if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != NULL) - if (xml_find_type(x0, NULL, "xmlns", CX_ATTR)==NULL){ - if ((x0a = xml_dup(x1a)) == NULL) - goto done; - if (xml_addsub(x0, x0a) < 0) - goto done; - } - - ok: retval = 0; done: diff --git a/test/test_augment.sh b/test/test_augment.sh index fcbc9251..2cc1db1b 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -12,6 +12,8 @@ # 2. example-augment - urn:example:augment - mymod # (augmented): mandatory-leaf, me, other, # (uses/grouping): ip, port, lid, lport +# Note augment+state not tested here (need plugin), simple test in test_restconf.sh +# # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -217,13 +219,12 @@ new "restconf get augment json" expectpart "$(curl -s -i -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}} ' new "restconf get augment xml" -expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' 'e1mymod:some-new-iftypetrue808080e2fdditrueif:fddi808080e3fdditruemymod:you808080' - +expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' 'e1mymod:some-new-iftypetrue808080e2fdditrueif:fddi808080e3fdditruemymod:you808080' #e123' XML=$(cat <e123' +e1mymod:some-new-iftypetrue23' EOF ) @@ -240,10 +241,10 @@ new "restconf POST augment multi-namespace path e2 (middle path)" expectpart "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 '' new "restconf GET augment multi-namespace top" -expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","example-augment:ospf":{"reference-bandwidth":23},"example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' +expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' new "restconf GET augment multi-namespace level 1" -expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","example-augment:ospf":{"reference-bandwidth":23},"example-augment:port":80,"example-augment:lport":8080}\]}' +expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080}\]}' new "restconf GET augment multi-namespace cross" expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf)" 0 'HTTP/1.1 200 OK' '{"example-augment:ospf":{"reference-bandwidth":23}}' diff --git a/test/test_netconf.sh b/test/test_netconf.sh index b08d3af9..e0476c1e 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -102,7 +102,7 @@ cat < $tmp # new EOF new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^eth1true]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^eth1true]]>]]>$' # Too many quotes cat < $tmp # new @@ -110,7 +110,7 @@ cat < $tmp # new EOF new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^eth/0/0trueeth1truetruefalse9.2.3.424]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^eth/0/0trueeth1truetruefalse9.2.3.424]]>]]>$' new "netconf validate missing type" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^" @@ -163,7 +163,7 @@ new "netconf edit state operation should fail" expecteof "$clixon_netconf -qf $cfg" 0 'e0up]]>]]>' "^protocolinvalid-valueerrorState data not allowed]]>]]>" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^eth1ex:ethtrueup]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^eth1ex:ethtrueup42foo]]>]]>$' new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test_perf_state.sh b/test/test_perf_state.sh index 61a2359a..402cfd3f 100755 --- a/test/test_perf_state.sh +++ b/test/test_perf_state.sh @@ -90,7 +90,7 @@ expecteof "time $clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>" -expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^e1ex:ethtrueup]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^e1ex:ethtrueup42foo]]>]]>$' new "netconf get $perfreq single reqs" { time -p for (( i=0; i<$perfreq; i++ )); do @@ -101,7 +101,7 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}' # RESTCONF get new "restconf get test single req" -expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]} +expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface":[{"name":"e1","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]} ' new "restconf get $perfreq single reqs" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 80997b19..433f8d6b 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -194,12 +194,16 @@ new "restconf Add interfaces subtree eth/0/0 using POST" expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "" new "restconf Check eth/0/0 added config" -expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}} - ' +expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' + +new "restconf Check eth/0/0 GET augmented state level 1" +expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' + +new "restconf Check eth/0/0 GET augmented state level 2" +expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 '{"clixon-example:my-status":{"int":42,"str":"foo"}}' new "restconf Check eth/0/0 added state" -expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":\["42","41","43"\]}} - ' +expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":\["42","41","43"\]}}' new "restconf Re-post eth/0/0 which should generate error" expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' @@ -211,7 +215,7 @@ new "Add nothing using POST (expect fail)" expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}' new "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} ' new "restconf delete eth/0/0" @@ -227,7 +231,7 @@ new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" new "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} ' new "restconf rpc using POST json" diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index ccb1de91..e5631a9c 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -99,7 +99,7 @@ expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://loca # This just catches the header and the jukebox module, the RFC has foo and bar which # seems wrong to recreate new "B.1.2. Retrieve the Server Module Information" -expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":' '"module":\[{"name":"example-events","revision":\[null\],"namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"}' +expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-lib","revision":"2019-08-13","namespace":"http://clicon.org/lib","conformance-type":"implement"},{"name":"clixon-rfc5277","revision":"2008-07-01","namespace":"urn:ietf:params:xml:ns:netmod:notification","conformance-type":"implement"},{"name":"example-events","revision":\[null\],"namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"' new "B.1.3. Retrieve the Server Capability Information" expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' 'urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=expliciturn:ietf:params:restconf:capability:depth