diff --git a/CHANGELOG.md b/CHANGELOG.md index 500ebf44..00a27c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Users may have to change how they access the system +* Error-type changed from protocol to application for data-not-unique netconf/restconf errors * New clixon-config@2020-11-03.yang revision * Moved to clixon-restconf.yang and marked as obsolete: - CLICON_RESTCONF_IPV4_ADDR @@ -63,6 +64,7 @@ Developers may need to change their code ### Minor changes +* Added new revision of main example yang: `clixon-example@2020-12-01.yang` * Support for building static lib: `LINKAGE=static configure` * Change comment character to be active anywhere to beginning of _word_ only. * See [Change CLIgen comments](https://github.com/clicon/cligen/issues/55) @@ -72,6 +74,9 @@ Developers may need to change their code ### Corrected Bugs +* Fixed [YANG: key statement in rpc/notification list #148](https://github.com/clicon/clixon/issues/148) + * Do not check uniqueness among lists without keys + * Fixed typo: [False Header Content_type in restconf error #152](https://github.com/clicon/clixon/issues/152) * Added message-id attributes in error and hello replies * See [namespace prefix nc is not supported in full #154](https://github.com/clicon/clixon/issues/154) diff --git a/example/main/Makefile.in b/example/main/Makefile.in index 2e27c993..afc2d062 100644 --- a/example/main/Makefile.in +++ b/example/main/Makefile.in @@ -81,7 +81,7 @@ all: $(PLUGINS) CLISPECS = $(APPNAME)_cli.cli -YANGSPECS = clixon-example@2020-03-11.yang +YANGSPECS = clixon-example@2020-12-01.yang # Backend plugin BE_SRC = $(APPNAME)_backend.c diff --git a/example/main/clixon-example@2020-03-11.yang b/example/main/clixon-example@2020-03-11.yang index 58435d0c..a4b0c23b 100644 --- a/example/main/clixon-example@2020-03-11.yang +++ b/example/main/clixon-example@2020-03-11.yang @@ -26,9 +26,6 @@ module clixon-example { import iana-if-type { prefix ianaift; } - import ietf-datastores { - prefix ds; - } /* Example interface type for tests, local callbacks, etc */ identity eth { base if:interface-type; @@ -36,21 +33,17 @@ module clixon-example { identity loopback { base if:interface-type; } - /* Generic config data */ - container table{ - list parameter{ - key name; - leaf name{ + /* Translation function example - See also example_cli */ + container translate{ + description "dont have lists directly under top since restconf cant address list directly"; + list translate{ + key k; + leaf k{ type string; } leaf value{ type string; } - leaf stat{ - description "Inline state data for example application"; - config false; - type int32; - } } } /* State data (not config) for the example application*/ @@ -89,6 +82,7 @@ module clixon-example { ex:e4 arg1{ uses bar; } + /* Example notification as used in RFC 5277 and RFC 8040 */ notification event { description "Example notification event."; diff --git a/example/main/clixon-example@2020-12-01.yang b/example/main/clixon-example@2020-12-01.yang new file mode 100644 index 00000000..fe5a4619 --- /dev/null +++ b/example/main/clixon-example@2020-12-01.yang @@ -0,0 +1,223 @@ +module clixon-example { + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + description + "Clixon example used as a part of the Clixon test suite. + It can be used as a basis for making new Clixon applications. + Note, may change without updating revision, just for testing current master. + "; + revision 2020-12-01 { + description "Added table/paramater/value as the primary data example"; + } + revision 2020-03-11 { + description "Added container around translation list. Released in Clixon 4.4.0"; + } + 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"; + } + revision 2019-01-13 { + description "Released in Clixon 3.9"; + } + import ietf-interfaces { + prefix if; + } + import ietf-ip { + prefix ip; + } + import iana-if-type { + prefix ianaift; + } + import ietf-datastores { + prefix ds; + } + /* Example interface type for tests, local callbacks, etc */ + identity eth { + base if:interface-type; + } + identity loopback { + base if:interface-type; + } + /* Generic config data */ + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + leaf stat{ + description "Inline state data for example application"; + config false; + type int32; + } + } + } + /* State data (not config) for the example application*/ + container state { + config false; + description "state data for the example application (must be here for example get operation)"; + leaf-list op { + 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 + "The first child of the ex:e4 (unknown) statement is inserted into + the module as a regular data statement. This means that 'uses bar;' + in the ex:e4 statement below is a valid data node"; + argument arg; + } + grouping bar { + leaf bar{ + type string; + } + } + ex:e4 arg1{ + uses bar; + } + /* Example notification as used in RFC 5277 and RFC 8040 */ + notification event { + description "Example notification event."; + leaf event-class { + type string; + description "Event class identifier."; + } + container reportingEntity { + description "Event specific information."; + leaf card { + type string; + description "Line card identifier."; + } + } + leaf severity { + type string; + description "Event severity description."; + } + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf x { + type string; + } + } + output { + leaf x { + type string; + } + } + } + rpc empty { + description "Smallest possible RPC with no input or output sections"; + } + rpc optional { + description "Small RPC with optional input and output"; + input { + leaf x { + type string; + } + } + output { + leaf x { + type string; + } + } + } + rpc example { + description "Some example input/output for testing RFC7950 7.14. + RPC simply echoes the input for debugging."; + input { + leaf x { + description + "If a leaf in the input tree has a 'mandatory' statement with + the value 'true', the leaf MUST be present in an RPC invocation."; + type string; + mandatory true; + } + leaf y { + description + "If a leaf in the input tree has a 'mandatory' statement with the + value 'true', the leaf MUST be present in an RPC invocation."; + type string; + default "42"; + } + leaf-list z { + description + "If a leaf-list in the input tree has one or more default + values, the server MUST use these values (XXX not supported)"; + type string; + } + leaf w { + description + "If any node has a 'when' statement that would evaluate to + 'false',then this node MUST NOT be present in the input tree. + (XXX not supported)"; + type string; + when "/translate/k=5/value='w'"; + } + list u0 { + description "list without key"; + leaf uk{ + type string; + } + } + list u1 { + description "list with key"; + key uk; + leaf uk{ + type string; + } + leaf val{ + type string; + } + } + } + output { + leaf x { + type string; + } + leaf y { + type string; + } + leaf z { + type string; + } + leaf w { + type string; + } + list u0 { + leaf uk{ + type string; + } + } + list u1 { + key uk; + leaf uk{ + type string; + } + } + } + } +} diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 197ba0df..3c9dccc9 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1184,7 +1184,7 @@ netconf_data_not_unique_xml(cxobj **xret, if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) goto done; if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, - "protocol" + "application" "operation-failed" "data-not-unique" "error") < 0) diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index fa9bec6f..b9163a7b 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -654,11 +654,15 @@ check_insert_duplicate(char **vec, * @param[in] xt The parent of x * @param[in] y Its yang spec (Y_LIST) * @param[in] yu A yang unique spec (Y_UNIQUE) for unique keyword or (Y_LIST) for list keys - * @param[out] xret Error XML tree. Free with xml_free after use + * @param[out] xret Error XML tree. Free with xml_free after use * @retval 1 Validation OK * @retval 0 Validation failed (cbret set) * @retval -1 Error * @note It would be possible to cache the vector built below + * All key leafs MUST be present for all list entries. + * The combined values of all the leafs specified in the key are used to + * uniquely identify a list entry. All key leafs MUST be given values + * when a list entry is created. */ static int check_unique_list(cxobj *x, @@ -666,7 +670,6 @@ check_unique_list(cxobj *x, yang_stmt *y, yang_stmt *yu, cxobj **xret) - { int retval = -1; cvec *cvk; /* unique vector */ @@ -686,7 +689,11 @@ check_unique_list(cxobj *x, sorted = (yang_keyword_get(yu) == Y_LIST && yang_find(y, Y_ORDERED_BY, "user") == NULL); cvk = yang_cvec_get(yu); - vlen = cvec_len(cvk); /* nr of unique elements to check */ + /* nr of unique elements to check */ + if ((vlen = cvec_len(cvk)) == 0){ + /* No keys: no checks necessary */ + goto ok; + } if ((vec = calloc(vlen*xml_child_nr(xt), sizeof(char*))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; @@ -718,6 +725,7 @@ check_unique_list(cxobj *x, x = xml_child_each(xt, x, CX_ELMNT); i++; } while (x && y == xml_spec(x)); /* stop if list ends, others may follow */ + ok: /* It would be possible to cache vec here as an optimization */ retval = 1; done: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index c80fa34d..d986426d 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2853,7 +2853,7 @@ yang_config(yang_stmt *ys) * * config statement is default true. * @param[in] ys Yang statement - * @retval 0 Node or one of its ancestor has config false + * @retval 0 Node or one of its ancestor has config false or is RPC or notification * @retval 1 Neither node nor any of its ancestors has config false */ int @@ -2865,6 +2865,8 @@ yang_config_ancestor(yang_stmt *ys) do { if (yang_config(yp) == 0) return 0; + if (yang_keyword_get(yp) == Y_INPUT || yang_keyword_get(yp) == Y_OUTPUT || yang_keyword_get(yp) == Y_NOTIFICATION) + return 0; } while((yp = yang_parent_get(yp)) != NULL); return 1; } diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index fe2dba69..54ca4200 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -1091,7 +1091,7 @@ ys_schemanode_check(yang_stmt *ys, return retval; } -/*! Check lists: non-config lists MUST have keys +/*! Check lists: config lists MUST have keys * @param[in] h Clicon handle * @param[in] ys Yang statement * Verify the following rule: diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 5d2c110b..7312fde4 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -144,7 +144,7 @@ new "restconf DELETE" expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 204 No Content" new "restconf POST from top containing duplicate keys expect error" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:cont1":{"interface":[{"name":"TEST","type":"eth0"},{"name":"TEST","type":"eth0"}]}}')" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-app-tag":"data-not-unique","error-severity":"error","error-info":{"non-unique":{"name":"TEST"}}}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:cont1":{"interface":[{"name":"TEST","type":"eth0"},{"name":"TEST","type":"eth0"}]}}')" 0 "HTTP/1.1 412 Precondition Failed" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"operation-failed","error-app-tag":"data-not-unique","error-severity":"error","error-info":{"non-unique":{"name":"TEST"}}}}}' new "restconf GET null datastore" expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 404 Not Found" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 3bbd3242..d5d2fc41 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -161,7 +161,24 @@ new "netconf wrong rpc namespace: should fail" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationunknown-elementgeterrorUnrecognized RPC (wrong namespace?)]]>]]>$" new "restconf wrong rpc: should fail" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" u $RCPROTO://localhost/restconf/operations/clixon-foo:get)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-foo:get)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}}' + +# test rpc lists with / without keys +LIST='foobarbar' +new "netconf example rpc input list without key with non-unique entries" +expecteof "$clixon_netconf -qf $cfg" 0 "mandatory$LIST]]>]]>" "^mandatory42$LIST]]>]]>$" + +LIST='bar1foo2' +new "netconf example rpc input list with key" +expecteof "$clixon_netconf -qf $cfg" 0 "mandatory$LIST]]>]]>" "^mandatory42$LIST]]>]]>$" + +LIST='bar12' +new "netconf example rpc input key list without key (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "mandatory$LIST]]>]]>" "^applicationmissing-elementukerrorMandatory key]]>]]>$" + +LIST='bar1bar2' +new "netconf example rpc input list with non-unique keys (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "mandatory$LIST]]>]]>" "^applicationoperation-faileddata-not-uniqueerrorbar]]>]]>$" if [ $RC -ne 0 ]; then new "Kill restconf daemon" diff --git a/test/test_unique.sh b/test/test_unique.sh index caf66609..6050633a 100755 --- a/test/test_unique.sh +++ b/test/test_unique.sh @@ -109,7 +109,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -138,7 +138,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" new "make it valid by deleting port from smtp entry" expecteof "$clixon_netconf -qf $cfg" 0 "nonesmtp25 @@ -163,7 +163,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^protocoloperation-faileddata-not-uniqueerror192.0.2.1]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-faileddata-not-uniqueerror192.0.2.1]]>]]>$" new "make valid by replacing IP of http entry" expecteof "$clixon_netconf -qf $cfg" 0 "nonehttp178.23.34.1 @@ -204,7 +204,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "netconf validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"