* 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.
* The multi-namespace augment state may rearrange the XML namespace attributes.
* Mandatory variables can no longer be deleted.
This commit is contained in:
Olof hagsand 2019-11-11 21:03:11 +01:00
parent 728fe9c6ac
commit 835f9030d2
20 changed files with 299 additions and 229 deletions

View file

@ -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)

View file

@ -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)

View file

@ -118,10 +118,14 @@ generic_validate(clicon_handle h,
for (i=0; i<td->td_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 */

View file

@ -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;

View file

@ -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": {

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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, "<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">");
for (i=0; i<xlen; i++){
name = xml_body(xvec[i]);
cprintf(cb, "<interface><name>%s</name><oper-status>up</oper-status></interface>", name);
}
cprintf(cb, "</interfaces>");
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, "<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">");
for (i=0; i<xlen; i++){
name = xml_body(xvec[i]);
cprintf(cb, "<interface xmlns:ex=\"urn:example:clixon\"><name>%s</name><type>ex:eth</type><oper-status>up</oper-status>", name);
cprintf(cb, "<ex:my-status><ex:int>42</ex:int><ex:str>foo</ex:str></ex:my-status>");
cprintf(cb, "</interface>");
}
cprintf(cb, "</interfaces>");
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, "<events xmlns=\"urn:example:events\">");
cprintf(cb, "<event><name>interface-down</name><event-count>90</event-count></event>");
cprintf(cb, "<event><name>interface-up</name><event-count>77</event-count></event>");
cprintf(cb, "</events>");
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, "<events xmlns=\"urn:example:events\">");
for (i=0; i<xlen; i++){
name = xml_body(xvec[i]);
c = 0;
for (j=0; j<strlen(name); j++)
c ^= name[j];
cprintf(cb, "<event><name>%s</name><event-count>%d</event-count></event>", name, c);
}
cprintf(cb, "</events>");
if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
goto done;
}
}
ok:
retval = 0;
done:

View file

@ -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);

View file

@ -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);

View file

@ -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,

View file

@ -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: <rpc-reply><rpc-error>
* @param[in] xerr Netconf error message on the level: <rpc-error>
* @param[out] cberr Translation from netconf err to cbuf. Free with cbuf_free.
* @retval 0 OK, with cberr set
* @retval -1 Error

View file

@ -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: "<prefix>: <error>"
* @param[in] xerr Netconf error message on the level: <rpc-reply><rpc-error>
* @param[in] xerr Netconf error message on the level: <rpc-error>
*/
int
clicon_rpc_generate_error(char *prefix,
cxobj *xerr)
clicon_rpc_generate_error(const char *prefix,
cxobj *xerr)
{
int retval = -1;
cbuf *cb = NULL;

View file

@ -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:

View file

@ -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 ' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e2</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:other>if:fddi</mymod:other><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e3</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:me>mymod:you</mymod:me><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface></interfaces>'
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 ' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface><name>e2</name><type>fddi</type><mymod:mandatory-leaf xmlns:mymod="urn:example:augment">true</mymod:mandatory-leaf><mymod:other xmlns:mymod="urn:example:augment">if:fddi</mymod:other><mymod:port xmlns:mymod="urn:example:augment">80</mymod:port><mymod:lport xmlns:mymod="urn:example:augment">8080</mymod:lport></interface><interface><name>e3</name><type>fddi</type><mymod:mandatory-leaf xmlns:mymod="urn:example:augment">true</mymod:mandatory-leaf><mymod:me xmlns:mymod="urn:example:augment">mymod:you</mymod:me><mymod:port xmlns:mymod="urn:example:augment">80</mymod:port><mymod:lport xmlns:mymod="urn:example:augment">8080</mymod:lport></interface></interfaces>'
#<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
XML=$(cat <<EOF
<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><name>e1</name><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces" xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
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}}'

View file

@ -102,7 +102,7 @@ cat <<EOF > $tmp # new
EOF
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
# Too many quotes
cat <<EOF > $tmp # new
@ -110,7 +110,7 @@ cat <<EOF > $tmp # new
EOF
new "netconf get config xpath parent"
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><name>eth1</name><enabled>true</enabled><ip:ipv4><ip:enabled>true</ip:enabled><ip:forwarding>false</ip:forwarding><ip:address><ip:ip>9.2.3.4</ip:ip><ip:prefix-length>24</ip:prefix-length></ip:address></ip:ipv4></interface></interfaces></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 "$(cat $tmp)" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ip:ipv4 xmlns:ip="urn:ietf:params:xml:ns:yang:ietf-ip"><ip:enabled>true</ip:enabled><ip:forwarding>false</ip:forwarding><ip:address><ip:ip>9.2.3.4</ip:ip><ip:prefix-length>24</ip:prefix-length></ip:address></ip:ipv4></interface></interfaces></data></rpc-reply>]]>]]>$'
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
@ -163,7 +163,7 @@ new "netconf edit state operation should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e0</name><oper-status>up</oper-status></interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>invalid-value</error-tag><error-severity>error</error-severity><error-message>State data not allowed</error-message></rpc-error></rpc-reply>]]>]]>"
new "netconf get state operation"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get><filter type=\"xpath\" select=\"/if:interfaces\" xmlns:if=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\" /></get></rpc>]]>]]>" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status></interface></interfaces></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get><filter type=\"xpath\" select=\"/if:interfaces\" xmlns:if=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\" /></get></rpc>]]>]]>" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status><ex:my-status xmlns:ex="urn:example:clixon"><ex:int>42</ex:int><ex:str>foo</ex:str></ex:my-status></interface></interfaces></data></rpc-reply>]]>]]>$'
new "netconf lock/unlock"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]><rpc><unlock><target><candidate/></target></unlock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -90,7 +90,7 @@ expecteof "time $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-
new "netconf get test single req"
sel="/if:interfaces/if:interface[if:name='e1']"
msg="<rpc><get><filter type=\"xpath\" select=\"$sel\" xmlns:if=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"/></get></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status></interface></interfaces></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status><ex:my-status xmlns:ex="urn:example:clixon"><ex:int>42</ex:int><ex:str>foo</ex:str></ex:my-status></interface></interfaces></data></rpc-reply>]]>]]>$'
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"

View file

@ -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"

View file

@ -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' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability><capability>urn:ietf:params:restconf:capability:depth</capability>