Added option to treat unknown XML (wrt YANG) as anydata.

This commit is contained in:
Olof hagsand 2020-05-03 22:03:33 +02:00
parent 12d1b67250
commit dafc6d10e0
15 changed files with 80 additions and 8 deletions

View file

@ -50,6 +50,8 @@ Expected: May 2020
### Minor changes ### Minor changes
* Added option `CLICON_YANG_UNKNOWN_ANYDATA` to treat unknown XML (wrt YANG) as anydata.
* This is to be (very) forgiving but you need to accept eg unsynchronized YANG and XML
* Compile-time option: `USE_CLIGEN44` for running clixon-45 with cligen-44. * Compile-time option: `USE_CLIGEN44` for running clixon-45 with cligen-44.
* Temporary fix since cligen-45 have some non-backward compatible behaviour. * Temporary fix since cligen-45 have some non-backward compatible behaviour.
* Optimizations * Optimizations

View file

@ -523,7 +523,6 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
return -1; return -1;
} }
/* External NACM file? */ /* External NACM file? */
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode && strcmp(nacm_mode, "external") == 0) if (nacm_mode && strcmp(nacm_mode, "external") == 0)
@ -692,6 +691,9 @@ main(int argc,
clicon_configfile(h)); clicon_configfile(h));
goto done; goto done;
} }
/* Treat unknown XML as anydata */
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
xml_bind_yang_unknown_anydata(1);
/* Publish stream on pubsub channels. /* Publish stream on pubsub channels.
* CLICON_STREAM_PUB should be set to URL to where streams are published * CLICON_STREAM_PUB should be set to URL to where streams are published

View file

@ -515,6 +515,10 @@ main(int argc, char **argv)
if (netconf_module_features(h) < 0) if (netconf_module_features(h) < 0)
goto done; goto done;
/* Treat unknwon XML as anydata */
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
xml_bind_yang_unknown_anydata(1);
/* Create top-level and store as option */ /* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;

View file

@ -715,6 +715,9 @@ main(int argc,
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
clicon_dbspec_yang_set(h, yspec); clicon_dbspec_yang_set(h, yspec);
/* Treat unknown XML as anydata */
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
xml_bind_yang_unknown_anydata(1);
/* Load restconf plugins before yangs are loaded (eg extension callbacks) */ /* Load restconf plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_restconf_dir(h)) != NULL) if ((dir = clicon_restconf_dir(h)) != NULL)

View file

@ -43,6 +43,7 @@
/* /*
* Prototypes * Prototypes
*/ */
int xml_bind_yang_unknown_anydata(int bool);
int xml_bind_yang_rpc(cxobj *xrpc, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang_rpc(cxobj *xrpc, yang_stmt *yspec, cxobj **xerr);
int xml_bind_yang_rpc_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang_rpc_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr);
int xml_bind_yang0(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang0(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr);

View file

@ -563,7 +563,9 @@ clicon_option_bool(clicon_handle h,
return 0; return 0;
if (strcmp(s,"true")==0) if (strcmp(s,"true")==0)
return 1; return 1;
return 0; /* Hopefully false, but anything else than "true" */ if (strcmp(s,"1")==0)
return 1;
return 0; /* Hopefully false, but anything else than "true" or "one" */
} }
/*! Set option given as bool /*! Set option given as bool
@ -578,8 +580,14 @@ clicon_option_bool_set(clicon_handle h,
{ {
char s[64]; char s[64];
if (snprintf(s, sizeof(s)-1, "%u", val) < 0) if (val != 0 && val != 1){
clicon_err(OE_CFG, EINVAL, "val is %d, 0 or 1 expected", val);
return -1; return -1;
}
if (snprintf(s, sizeof(s)-1, "%s", val?"true":"false") < 0){
clicon_err(OE_CFG, errno, "snprintf");
return -1;
}
return clicon_option_str_set(h, name, s); return clicon_option_str_set(h, name, s);
} }

View file

@ -1091,15 +1091,17 @@ xml_yang_validate_all(clicon_handle h,
and !Node has a config sub-statement and it is false */ and !Node has a config sub-statement and it is false */
ys=xml_spec(xt); ys=xml_spec(xt);
if (ys==NULL){ if (ys==NULL){
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
goto ok;
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
if (xml2ns(xt, xml_prefix(xt), &namespace) < 0)
goto done;
cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt)); cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt));
if ((xp = xml_parent(xt)) != NULL) if ((xp = xml_parent(xt)) != NULL)
cprintf(cb, " with parent: %s", xml_name(xp)); cprintf(cb, " with parent: %s", xml_name(xp));
if (xml2ns(xt, xml_prefix(xt), &namespace) < 0)
goto done;
if (namespace) if (namespace)
cprintf(cb, " in namespace: %s", namespace); cprintf(cb, " in namespace: %s", namespace);
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)

View file

@ -79,6 +79,21 @@
#include "clixon_yang_type.h" #include "clixon_yang_type.h"
#include "clixon_xml_bind.h" #include "clixon_xml_bind.h"
/*
* Local variables
*/
static int _yang_unknown_anydata = 0;
/*! Kludge to equate unknown XML with anydata
* The problem with this is that its global and shuld be bound to a handle
*/
int
xml_bind_yang_unknown_anydata(int bool)
{
_yang_unknown_anydata = bool;
return 0;
}
/*! After yang binding, bodies of containers and lists are stripped from XML bodies /*! After yang binding, bodies of containers and lists are stripped from XML bodies
* May apply to other nodes? * May apply to other nodes?
*/ */
@ -156,6 +171,10 @@ populate_self_parent(cxobj *xt,
if (xml2ns(xt, xml_prefix(xt), &ns) < 0) if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done; goto done;
if ((y = yang_find_datanode(yparent, name)) == NULL){ if ((y = yang_find_datanode(yparent, name)) == NULL){
if (_yang_unknown_anydata){
retval = 2; /* treat as anydata */
goto done;
}
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
@ -244,6 +263,10 @@ populate_self_top(cxobj *xt,
if (xml2ns(xt, xml_prefix(xt), &ns) < 0) if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done; goto done;
if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */ if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */
if (_yang_unknown_anydata){
retval = 2; /* treat as anydata */
goto done;
}
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;

View file

@ -106,6 +106,9 @@ fi
# Backend user # Backend user
BUSER=clicon BUSER=clicon
# If set, unknown XML is treated as ANYDATA
: ${YANG_UNKNOWN_ANYDATA:=false}
# Follow the binary programs that can be parametrized (eg with valgrind) # Follow the binary programs that can be parametrized (eg with valgrind)
: ${clixon_cli:=clixon_cli} : ${clixon_cli:=clixon_cli}

View file

@ -150,7 +150,9 @@ new "copy startup->candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "copy startup->running not allowed" new "copy startup->running not allowed"
if ! $YANG_UNKNOWN_ANYDATA ; then
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><running/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>running</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: running with parent: target in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><running/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>running</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: running with parent: target in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
fi
# Here running is empty # Here running is empty
new "Check running empty" new "Check running empty"

View file

@ -282,8 +282,10 @@ new "restconf delete identity"
expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypto)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypto)" 0 "HTTP/1.1 204 No Content"
# 2. set identity in other module with restconf , read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf
if ! $YANG_UNKNOWN_ANYDATA ; then
new "restconf add POST instead of PUT (should fail)" new "restconf add POST instead of PUT (should fail)"
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}'
fi
# Alternative error: # Alternative error:
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'

View file

@ -237,8 +237,10 @@ new "restconf rpc using POST json"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
' '
if ! $YANG_UNKNOWN_ANYDATA ; then
new "restconf rpc using POST json wrong" new "restconf rpc using POST json wrong"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
fi
new "restconf rpc non-existing rpc without namespace" new "restconf rpc non-existing rpc without namespace"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'

View file

@ -122,8 +122,10 @@ fi
new "restconf omit mandatory" new "restconf omit mandatory"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}} '
new "restconf add extra" new "restconf add extra w/o yang: should fail"
if ! $YANG_UNKNOWN_ANYDATA ; then
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: example in namespace: urn:example:clixon"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: example in namespace: urn:example:clixon"}}}'
fi
new "restconf wrong method" new "restconf wrong method"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}} '
@ -137,8 +139,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netco
new "netconf edit-config ok" new "netconf edit-config ok"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
if ! $YANG_UNKNOWN_ANYDATA ; then
new "netconf edit-config extra arg should fail" new "netconf edit-config extra arg should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: extra with parent: edit-config in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: extra with parent: edit-config in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
fi
new "netconf edit-config empty target should fail" new "netconf edit-config empty target should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target/><config/></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>data-missing</error-tag><error-app-tag>missing-choice</error-app-tag><error-info><missing-choice>config-target</missing-choice></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target/><config/></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>data-missing</error-tag><error-app-tag>missing-choice</error-app-tag><error-info><missing-choice>config-target</missing-choice></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$'

View file

@ -66,7 +66,7 @@
#include "clixon/clixon.h" #include "clixon/clixon.h"
/* Command line options passed to getopt(3) */ /* Command line options passed to getopt(3) */
#define UTIL_XML_OPTS "hD:f:Jjl:pvoy:Y:t:T:" #define UTIL_XML_OPTS "hD:f:Jjl:pvoy:Y:t:T:u"
static int static int
validate_tree(clicon_handle h, validate_tree(clicon_handle h,
@ -125,6 +125,7 @@ usage(char *argv0)
"\t-Y <dir> \tYang dirs (can be several)\n" "\t-Y <dir> \tYang dirs (can be several)\n"
"\t-t <file>\tXML top input file (where base tree is pasted to)\n" "\t-t <file>\tXML top input file (where base tree is pasted to)\n"
"\t-T <path>\tXPath to where in top input file base should be pasted\n" "\t-T <path>\tXPath to where in top input file base should be pasted\n"
"\t-u \t\tTreat unknown XML as anydata\n"
, ,
argv0); argv0);
exit(0); exit(0);
@ -221,6 +222,11 @@ main(int argc,
case 'T': /* top file xpath */ case 'T': /* top file xpath */
top_path = optarg; top_path = optarg;
break; break;
case 'u':
if (clicon_option_bool_set(h, "CLICON_YANG_UNKNOWN_ANYDATA", 1) < 0)
goto done;
xml_bind_yang_unknown_anydata(1);
break;
default: default:
usage(argv[0]); usage(argv[0]);
break; break;

View file

@ -314,6 +314,14 @@ module clixon-config {
Some yang specs seem not to fulfil this. However, if you reset this, there may Some yang specs seem not to fulfil this. However, if you reset this, there may
be follow-up errors due to code that assumes a configuration list has keys"; be follow-up errors due to code that assumes a configuration list has keys";
} }
leaf CLICON_YANG_UNKNOWN_ANYDATA{
type boolean;
default false;
description
"Treat unknown XML/JSON nodes as anydata.
This does not apply to namespaces, which means a top-level node: xxx:yyy
is accepted only if yyy is unknown, not xxx";
}
leaf CLICON_BACKEND_DIR { leaf CLICON_BACKEND_DIR {
type string; type string;
description description