diff --git a/CHANGELOG.md b/CHANGELOG.md index 207c5843..11165081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ Expected: May 2020 ### 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. * Temporary fix since cligen-45 have some non-backward compatible behaviour. * Optimizations diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index bf90e137..91c4f9c5 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -523,7 +523,6 @@ main(int argc, usage(h, argv[0]); return -1; } - /* External NACM file? */ nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); if (nacm_mode && strcmp(nacm_mode, "external") == 0) @@ -692,6 +691,9 @@ main(int argc, clicon_configfile(h)); 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. * CLICON_STREAM_PUB should be set to URL to where streams are published diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 9ad836ce..0a3a9fa4 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -515,6 +515,10 @@ main(int argc, char **argv) if (netconf_module_features(h) < 0) 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 */ if ((yspec = yspec_new()) == NULL) goto done; diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 67a0dc6f..8a9bb9da 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -715,6 +715,9 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; 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) */ if ((dir = clicon_restconf_dir(h)) != NULL) diff --git a/lib/clixon/clixon_xml_bind.h b/lib/clixon/clixon_xml_bind.h index eff92121..55818d81 100644 --- a/lib/clixon/clixon_xml_bind.h +++ b/lib/clixon/clixon_xml_bind.h @@ -43,6 +43,7 @@ /* * 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_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr); int xml_bind_yang0(cxobj *xt, yang_bind yb, yang_stmt *yspec, cxobj **xerr); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index bc78f092..1798f6f9 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -555,7 +555,7 @@ clicon_option_int_set(clicon_handle h, */ int clicon_option_bool(clicon_handle h, - const char *name) + const char *name) { char *s; @@ -563,7 +563,9 @@ clicon_option_bool(clicon_handle h, return 0; if (strcmp(s,"true")==0) 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 @@ -578,8 +580,14 @@ clicon_option_bool_set(clicon_handle h, { 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; + } + 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); } diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index b7705dbc..16466235 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -1091,15 +1091,17 @@ xml_yang_validate_all(clicon_handle h, and !Node has a config sub-statement and it is false */ ys=xml_spec(xt); if (ys==NULL){ + if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) + goto ok; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); 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)); if ((xp = xml_parent(xt)) != NULL) cprintf(cb, " with parent: %s", xml_name(xp)); + if (xml2ns(xt, xml_prefix(xt), &namespace) < 0) + goto done; if (namespace) cprintf(cb, " in namespace: %s", namespace); if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 9bac9ed7..98cd10c7 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -79,6 +79,21 @@ #include "clixon_yang_type.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 * May apply to other nodes? */ @@ -156,6 +171,10 @@ populate_self_parent(cxobj *xt, if (xml2ns(xt, xml_prefix(xt), &ns) < 0) goto done; if ((y = yang_find_datanode(yparent, name)) == NULL){ + if (_yang_unknown_anydata){ + retval = 2; /* treat as anydata */ + goto done; + } if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; @@ -244,6 +263,10 @@ populate_self_top(cxobj *xt, if (xml2ns(xt, xml_prefix(xt), &ns) < 0) goto done; 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){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; diff --git a/test/lib.sh b/test/lib.sh index 4a2c82aa..d4d2277b 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -106,6 +106,9 @@ fi # Backend user 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) : ${clixon_cli:=clixon_cli} diff --git a/test/test_copy_config.sh b/test/test_copy_config.sh index 07d1c8b7..1aad72ab 100755 --- a/test/test_copy_config.sh +++ b/test/test_copy_config.sh @@ -150,7 +150,9 @@ new "copy startup->candidate" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "copy startup->running not allowed" +if ! $YANG_UNKNOWN_ANYDATA ; then expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationunknown-elementrunningerrorFailed to find YANG spec of XML node: running with parent: target in namespace: urn:ietf:params:xml:ns:netconf:base:1.0]]>]]>$" +fi # Here running is empty new "Check running empty" diff --git a/test/test_identity.sh b/test/test_identity.sh index 080e1d4c..693a4703 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -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" # 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)" 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: #'{"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"}}}' diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c6449270..e0500dbc 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -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"}} ' +if ! $YANG_UNKNOWN_ANYDATA ; then 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"}}}' +fi 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"}}' diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 170987b9..7aa7f0f7 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -122,8 +122,10 @@ fi 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"}}} ' -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"}}}' +fi 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"}}} ' @@ -137,8 +139,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" +if ! $YANG_UNKNOWN_ANYDATA ; then new "netconf edit-config extra arg should fail" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerrorFailed to find YANG spec of XML node: extra with parent: edit-config in namespace: urn:ietf:params:xml:ns:netconf:base:1.0]]>]]>$" +fi new "netconf edit-config empty target should fail" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationdata-missingmissing-choiceconfig-targeterror]]>]]>$' diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index b746512a..ddc8d3cd 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -66,7 +66,7 @@ #include "clixon/clixon.h" /* 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 validate_tree(clicon_handle h, @@ -125,6 +125,7 @@ usage(char *argv0) "\t-Y \tYang dirs (can be several)\n" "\t-t \tXML top input file (where base tree is pasted to)\n" "\t-T \tXPath to where in top input file base should be pasted\n" + "\t-u \t\tTreat unknown XML as anydata\n" , argv0); exit(0); @@ -221,6 +222,11 @@ main(int argc, case 'T': /* top file xpath */ top_path = optarg; 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: usage(argv[0]); break; diff --git a/yang/clixon/clixon-config@2020-02-22.yang b/yang/clixon/clixon-config@2020-02-22.yang index 73205138..ee335010 100644 --- a/yang/clixon/clixon-config@2020-02-22.yang +++ b/yang/clixon/clixon-config@2020-02-22.yang @@ -314,6 +314,14 @@ module clixon-config { 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"; } + 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 { type string; description