diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8aad59..ee315073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ ### Major changes: +* (Work in progress) Restconf error handling for get and edit operations + ### Minor changes: +* Add username to rpc calls to prepare for authorization for backend: + clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) + clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) + * Experimental: Added CLICON_TRANSACTION_MOD configurqation option. If set, modifications in validation and commit callbacks are written back into the datastore. @@ -31,7 +37,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. ## 3.5.0 (12 February 2018) ### Major changes: -* Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right. +* Major Restconf feature update to comply to RFC 8040. Thanks Stephen Jones for getting right. * GET: Always return object referenced (and nothing else). ie, GET /restconf/data/X returns X. * GET Added support for the following resources: Well-known, top-level resource, and yang library version, * GET Single element JSON lists use {list:[element]}, not {list:element}. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 023b531d..67fa9cec 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -238,7 +238,6 @@ yang2cli_var_sub(clicon_handle h, goto done; } } - else{ /* Cligen does not have 'max' keyword in range so need to find actual max value of type if yang range expression is 0..max */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 1bef74fc..5ff69548 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -144,47 +144,62 @@ api_data_options(clicon_handle h, * @param[in] h Clixon handle * @param[in] r Fastcgi request handle * @param[in] xerr XML error message from backend + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML */ static int -api_data_get_err(clicon_handle h, - FCGX_Request *r, - cxobj *xerr) +api_return_err(clicon_handle h, + FCGX_Request *r, + cxobj *xerr, + int pretty, + int use_xml) { int retval = -1; - cbuf *cbj = NULL; + cbuf *cb = NULL; cxobj *xtag; int code; const char *reason_phrase; - if ((cbj = cbuf_new()) == NULL) + clicon_debug(1, "%s", __FUNCTION__); + if ((cb = cbuf_new()) == NULL) goto done; - if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ + if ((xtag = xpath_first(xerr, "error-tag")) == NULL){ notfound(r); /* bad reply? */ goto ok; } code = restconf_err2code(xml_body(xtag)); if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; - clicon_debug(1, "%s code:%d reason phrase:%s", - __FUNCTION__, code, reason_phrase); - if (xml_name_set(xerr, "error") < 0) goto done; - if (xml2json_cbuf(cbj, xerr, 1) < 0) - goto done; + if (use_xml){ + if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0) + goto done; + } + else + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, "{\r\n"); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n"); - FCGX_FPrintF(r->out, " %s", cbuf_get(cbj)); - FCGX_FPrintF(r->out, " }\r\n"); - FCGX_FPrintF(r->out, "}\r\n"); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", + use_xml?"xml":"json"); + if (use_xml){ + FCGX_FPrintF(r->out, " %s", cbuf_get(cb), pretty?"\r\n":""); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " \r\n"); + } + else{ + FCGX_FPrintF(r->out, "{%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, " %s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " }%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, "}\r\n"); + } ok: retval = 0; done: - if (cbj) - cbuf_free(cbj); + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); return retval; } @@ -269,9 +284,9 @@ api_data_get2(clicon_handle h, cbuf_free(cb); } #endif - /* Check if error return */ + /* Check if error return XXX this needs more work */ if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ - if (api_data_get_err(h, r, xerr) < 0) + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; } @@ -425,6 +440,7 @@ api_data_post(clicon_handle h, { int retval = -1; enum operation_type op = OP_CREATE; + int pretty; int i; cxobj *xdata = NULL; cbuf *cbx = NULL; @@ -437,10 +453,18 @@ api_data_post(clicon_handle h, cxobj *xu; char *media_content_type; int parse_xml = 0; /* By default expect and parse JSON */ - + cxobj *xret = NULL; + cxobj *xerr; + char *media_accept; + int use_xml = 0; /* By default use JSON */ + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); if (media_content_type && strcmp(media_content_type, "application/yang-data+xml")==0) @@ -499,14 +523,19 @@ api_data_post(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; + + cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - conflict(r); - goto ok; + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto done; } /* Assume this is validation failed since commit includes validate */ if (clicon_rpc_commit(h) < 0){ @@ -522,6 +551,8 @@ api_data_post(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); if (xtop) xml_free(xtop); if (xdata) diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9ba18073..cb150c0d 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -200,7 +200,7 @@ clicon_rpc_netconf_xml(clicon_handle h, return retval; } -/*! Generate clicon error function call from Netconf error message +/*! Generate and log clicon error function call from Netconf error message * @param[in] xerr Netconf error message on the level: */ int @@ -308,7 +308,7 @@ clicon_rpc_get_config(clicon_handle h, * @param[in] op Operation on database item: OP_MERGE, OP_REPLACE * @param[in] xml XML string. Ex: ..... * @retval 0 OK - * @retval -1 Error + * @retval -1 Error and logged to syslog * @note xml arg need to have as top element * @code * if (clicon_rpc_edit_config(h, "running", OP_MERGE, @@ -361,6 +361,8 @@ clicon_rpc_edit_config(clicon_handle h, * @param[in] h CLICON handle * @param[in] db1 src database, eg "running" * @param[in] db2 dst database, eg "startup" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_copy_config(h, "running", "startup") < 0) * err; @@ -396,6 +398,8 @@ clicon_rpc_copy_config(clicon_handle h, /*! Send a request to backend to delete a config database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_delete_config(h, "startup") < 0) * err; @@ -430,6 +434,8 @@ clicon_rpc_delete_config(clicon_handle h, /*! Lock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_lock(clicon_handle h, @@ -460,6 +466,8 @@ clicon_rpc_lock(clicon_handle h, /*! Unlock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_unlock(clicon_handle h, @@ -557,6 +565,8 @@ clicon_rpc_get(clicon_handle h, /*! Close a (user) session * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_close_session(clicon_handle h) @@ -586,6 +596,8 @@ clicon_rpc_close_session(clicon_handle h) /*! Kill other user sessions * @param[in] h CLICON handle * @param[in] session_id Session id of other user session + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_kill_session(clicon_handle h, @@ -616,7 +628,8 @@ clicon_rpc_kill_session(clicon_handle h, /*! Send validate request to backend daemon * @param[in] h CLICON handle * @param[in] db Name of database - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_validate(clicon_handle h, @@ -646,7 +659,8 @@ clicon_rpc_validate(clicon_handle h, /*! Commit changes send a commit request to backend daemon * @param[in] h CLICON handle - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_commit(clicon_handle h) @@ -674,8 +688,9 @@ clicon_rpc_commit(clicon_handle h) } /*! Discard all changes in candidate / revert to running - * @param[in] h CLICON handle - * @retval 0 OK + * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_discard_changes(clicon_handle h) @@ -707,6 +722,9 @@ clicon_rpc_discard_changes(clicon_handle h) * @param{in] stream name of notificatio/log stream (CLICON is predefined) * @param{in] filter message filter, eg xpath for xml notifications * @param[out] s0 socket returned where notification mesages will appear + * @retval 0 OK + * @retval -1 Error and logged to syslog + * @note When using netconf create-subsrciption,status and format is not supported */ int @@ -742,8 +760,10 @@ clicon_rpc_create_subscription(clicon_handle h, } /*! Send a debug request to backend server - * @param[in] h CLICON handle - * @param[in] level Debug level + * @param[in] h CLICON handle + * @param[in] level Debug level + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_debug(clicon_handle h, diff --git a/test/clixon b/test/clixon deleted file mode 100755 index 9c38654a..00000000 --- a/test/clixon +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh -# Top-level cron scripts. Add this to (for example) /etc/cron.daily - -err(){ - testname=$1 - errcode=$2 - echo "Error in [$testname]" - logger "CLIXON: Error in [$testname]" - exit $errcode -} - -# cd to working dir -cd /var/tmp -if [ $# -ne 0 ]; then - err "usage: $0" 0 -fi -rm -rf cligen -rm -rf clixon -git clone https://github.com/olofhagsand/cligen.git -if [ $? -ne 0 ]; then - err "git clone cligen" 1 -fi -cd cligen -CFLAGS=-Werror ./configure -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -cd .. -git clone https://github.com/clicon/clixon.git -if [ $? -ne 0 ]; then - err "git clone clixon" 1 -fi -cd clixon -CFLAGS=-Werror ./configure --with-cligen=../cligen -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install" 4 -fi -sudo make install-include -if [ $? -ne 0 ]; then - err "make install include" 5 - exit 1 -fi -cd example -make -if [ $? -ne 0 ]; then - err "make example" 6 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install example" 7 -fi -cd ../test -#./all.sh -(cd /home/olof/src/clixon/test; ./all.sh) -errcode=$? -if [ $errcode -ne 0 ]; then - err "test" $errcode -fi -cd ../.. -rm -rf clixon cligen -logger "CLIXON: tests OK" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 61073308..d09c7812 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -128,7 +128,6 @@ new "restconf get empty config + state json" expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" new "restconf get empty config + state xml" -# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) expect="eth0eth42" match=`echo $ret | grep -EZo "$expect"` @@ -161,8 +160,19 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' "" +new "restconf GET datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" "data" + +new "restconf Add subtree to datastore using POST" +ret=$(curl -s -i -X POST -H "Accept: application/yang-data+json" -d '{"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}}' http://localhost/restconf/data) +expect="HTTP/1.1 200 OK" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf Re-add subtree which should give error" +expectfn 'curl -s -i -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"error-tag": "operation-failed"' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} @@ -182,7 +192,7 @@ expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface $' new "restconf Re-post eth/0/0 which should generate error" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "Data resource already exists" +expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' 'Object to create already exists' new "Add leaf description using POST" expectfn 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index cdd2d309..55a23308 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -81,10 +81,10 @@ new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" new "restconf POST again" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Data resource already exis" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Object to create already exists" new "restconf POST from top" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Object to create already exists" new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" diff --git a/test/test_type.sh b/test/test_type.sh index dcc2288b..e63bd05a 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -4,8 +4,10 @@ # include err() and new() functions and creates $dir . ./lib.sh -fyang=$dir/type.yang + cfg=$dir/conf_yang.xml +fyang=$dir/type.yang + cat < $cfg @@ -70,6 +72,57 @@ module example{ enum down; } } + leaf length1 { + type string { + length "1"; + } + } +/* leaf length2 { + type string { + length "max"; + } + } + leaf length3 { + type string { + length "min"; + } + }*/ + leaf length4 { + type string { + length "4..4000"; + } + } +/* leaf length5 { + type string { + length "min..max"; + } + }*/ + leaf num1 { + type int32 { + range "1"; + } + } +/* leaf num2 { + type int32 { + range "min"; + } + } + leaf num3 { + type int32 { + range "max"; + } + } +*/ + leaf num4 { + type int32 { + range "4..4000"; + } + } +/* leaf num5 { + type int32 { + range "min..max"; + } + }*/ } EOF diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 3bcc0ce2..277c204d 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -304,7 +304,7 @@ module clixon-config { type boolean; default false; description "If set, modifications in validation and commit - callbacks will be saved into running"; + callbacks are written back into the datastore"; } } }