From 719ea9339817a3f2d3fe53ea9ab1d08d44031470 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 13 Jul 2018 16:31:39 +0200 Subject: [PATCH] Conformance to RFC-8040 operations where prefix was used instead of module name. * Proper specification for an operation is POST /restconf/operations/: HTTP/1.1 * See https://github.com/clicon/clixon/issues/31, https://github.com/clicon/clixon/pull/32 and https://github.com/clicon/clixon/issues/30 * Thanks David Cornejo and Dmitry Vakhrushev of Netgate for pointing this out --- CHANGELOG.md | 4 +++ apps/restconf/restconf_methods.c | 35 +++++++++++++++++------- lib/clixon/clixon_yang.h | 1 + lib/src/clixon_yang.c | 46 +++++++++++++++++++++++++++++--- test/test_restconf.sh | 26 +++++++++--------- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5448e813..0ec25fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 3.7.0 (Upcoming) ### Major changes: +* Conformance for RFC-8040 operations where prefix was used instead of module name. + * Proper specification for an operation is POST /restconf/operations/: HTTP/1.1 + * See https://github.com/clicon/clixon/issues/31, https://github.com/clicon/clixon/pull/32 and https://github.com/clicon/clixon/issues/30 + * Thanks David Cornejo and Dmitry Vakhrushev of Netgate for pointing this out. * Support for YANG identity and identityref according to RFC 7950 Sec 7.18 and 9.10 * Previous support did no validation of values. * Validation of types and CLI expansion diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 142105d0..62580cd6 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -925,8 +925,7 @@ api_operations_get(clicon_handle h, yang_spec *yspec; yang_stmt *ym; yang_stmt *yc; - yang_stmt *yprefix; - char *prefix; + char *modname; cbuf *cbx = NULL; cxobj *xt = NULL; @@ -937,15 +936,12 @@ api_operations_get(clicon_handle h, cprintf(cbx, ""); ym = NULL; while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { - if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) - prefix = yprefix->ys_argument; - else - continue; + modname = ym->ys_argument; yc = NULL; while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); + cprintf(cbx, "<%s:%s />", modname, yc->ys_argument); } } cprintf(cbx, ""); @@ -1030,6 +1026,9 @@ api_operations_post(clicon_handle h, char *username; cbuf *cbret = NULL; int ret = 0; + char *prefix = NULL; + char *id = NULL; + yang_stmt *ys = NULL; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1047,8 +1046,22 @@ api_operations_post(clicon_handle h, } clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); - /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, oppath, Y_RPC, &yrpc) < 0){ + /* Find yang rpc statement, return yang rpc statement if found + * POST {+restconf}/operations/ + * + * The field identifies the module name and rpc identifier + * string for the desired operation. + */ + if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ + goto done; + if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){ + if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) goto done; if (api_return_err(h, r, xerr, pretty, use_xml) < 0) @@ -1229,6 +1242,10 @@ api_operations_post(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (prefix) + free(prefix); + if (id) + free(id); if (xdata) xml_free(xdata); if (xtop) diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 5ea7e986..18fbee6b 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -246,6 +246,7 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); +int yang_nodeid_split(char *nodeid, char **prefix, char **id); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 8960fdca..d034798b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -611,8 +611,8 @@ yang_find_schemanode(yang_node *yn, /*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? - * @param[in] schemanode If set look for schema nodes, otherwise only data nodes + * @param[in] argument Name of node. If NULL match first + * @param[in] class See yang_class for class of yang nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for nodes. Note that if a child to a module is a choice, @@ -803,7 +803,7 @@ yarg_id(yang_stmt *ys) return id; } -/* Assume argument is id on the type: <[prefix:]id>, return 'prefix' +/*! Assume argument is id on the type: <[prefix:]id>, return 'prefix' * @param[in] ys A yang statement * @retval NULL No prefix * @retval prefix Malloced string that needs to be freed by caller. @@ -822,6 +822,46 @@ yarg_prefix(yang_stmt *ys) return prefix; } + +/*! Split yang node identifier into prefix and identifer. + * @param[in] node-id + * @param[out] prefix Malloced string. May be NULL. + * @param[out] id Malloced identifier. + * @retval 0 OK + * @retval -1 Error + * @note caller need to free id and prefix after use + */ +int +yang_nodeid_split(char *nodeid, + char **prefix, + char **id) +{ + int retval = -1; + char *str; + + if ((str = strchr(nodeid, ':')) == NULL){ + if ((*id = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + else{ + if ((*prefix = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + (*prefix)[str-nodeid] = '\0'; + str++; + if ((*id = strdup(str)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! Given a yang statement and a prefix, return yang module to that prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 5008ad80..f4856e81 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -92,7 +92,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -D sleep 1 @@ -113,12 +113,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect="" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -143,7 +143,7 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new2 "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" "" +expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" "" new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} @@ -246,29 +246,29 @@ expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' new2 "restconf rpc using POST json w/o mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' new2 "restconf rpc non-existing rpc w/o namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ex:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) expect="ipv42.3.4.5" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then @@ -276,10 +276,10 @@ if [ -z "$match" ]; then fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}} ' new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) expect="ok" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then @@ -287,7 +287,7 @@ if [ -z "$match" ]; then fi # XXX cant get -H to work -#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5' +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' # Cant get shell macros to work, inline matching from lib.sh