diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index d8cdb27d..309c5223 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -212,7 +212,7 @@ api_well_known(clicon_handle h,
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "\n");
FCGX_FPrintF(r->out, " \n");
- FCGX_FPrintF(r->out, "\n");
+ FCGX_FPrintF(r->out, "\r\n");
return 0;
}
@@ -260,7 +260,7 @@ api_root(clicon_handle h,
if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done;
FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):"");
- FCGX_FPrintF(r->out, "\n\n");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
retval = 0;
done:
if (cb)
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 78f96ece..fde9c4dc 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -189,12 +189,12 @@ api_return_err(clicon_handle h,
if (pretty){
FCGX_FPrintF(r->out, " \n", cbuf_get(cb));
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
- FCGX_FPrintF(r->out, " \n");
+ FCGX_FPrintF(r->out, " \r\n");
}
else {
FCGX_FPrintF(r->out, "", cbuf_get(cb));
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
- FCGX_FPrintF(r->out, "\n");
+ FCGX_FPrintF(r->out, "\r\n");
}
}
else{
@@ -202,13 +202,13 @@ api_return_err(clicon_handle h,
FCGX_FPrintF(r->out, "{\n");
FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n",
cbuf_get(cb));
- FCGX_FPrintF(r->out, "}\n");
+ FCGX_FPrintF(r->out, "}\r\n");
}
else{
FCGX_FPrintF(r->out, "{");
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : ");
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
- FCGX_FPrintF(r->out, "}\n");
+ FCGX_FPrintF(r->out, "}\r\n");
}
}
ok:
@@ -234,6 +234,7 @@ api_return_err(clicon_handle h,
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
+ * See RFC8040 Sec 4.2 and 4.3
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
@@ -337,10 +338,9 @@ api_data_get2(clicon_handle h,
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
goto done;
}
-
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
- FCGX_FPrintF(r->out, "\n\n");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
ok:
retval = 0;
done:
@@ -437,6 +437,8 @@ api_data_get(clicon_handle h,
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
* @note restconf POST is mapped to edit-config create.
+ * See RFC8040 Sec 4.4.1
+
POST:
target resource type is datastore --> create a top-level resource
target resource type is data resource --> create child resource
@@ -520,7 +522,7 @@ api_data_post(clicon_handle h,
badrequest(r);
goto ok;
}
- /* The message-body MUST contain exactly one instance of the
+ /* 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource.
*/
if (xml_child_nr(xdata) != 1){
@@ -649,6 +651,7 @@ match_list_keys(yang_stmt *y,
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
* @note restconf PUT is mapped to edit-config replace.
+ * See RFC8040 Sec 4.5
* @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
@@ -823,6 +826,7 @@ api_data_put(clicon_handle h,
* @param[in] data Stream input data
* @param[in] username Authenticated user
* Netconf: (nc:operation="merge")
+ * See RFC8040 Sec 4.6
*/
int
api_data_patch(clicon_handle h,
@@ -846,6 +850,7 @@ api_data_patch(clicon_handle h,
* @param[in] username Authenticated user
* @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
+ * See RFC 8040 Sec 4.7
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
* Netconf: (nc:operation="delete")
@@ -1020,9 +1025,9 @@ api_operations_get(clicon_handle h,
clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
- FCGX_FPrintF(r->out, "\n");
+ FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
- FCGX_FPrintF(r->out, "\n\n");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
// ok:
retval = 0;
done:
@@ -1046,7 +1051,7 @@ api_operations_get(clicon_handle h,
* @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 for output data
* @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data
-
+ * See RFC 8040 Sec 3.6 / 4.4.2
* @note We map post to edit-config create.
POST {+restconf}/operations/
*/
@@ -1193,7 +1198,7 @@ api_operations_post(clicon_handle h,
goto done;
clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
- FCGX_FPrintF(r->out, "\n\n");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
}
ok:
retval = 0;
diff --git a/doc/FAQ.md b/doc/FAQ.md
index ee04a409..0debf435 100644
--- a/doc/FAQ.md
+++ b/doc/FAQ.md
@@ -83,11 +83,11 @@ configuration file is installed at /usr/local/etc/routing.xml. The
YANG specification for the configuration file is clixon-config.yang.
You can change where CLixon looks for the configuration FILE as follows:
- # Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
- # Provide --with-configfile=FILE when configuring
- # Provide --with-sysconfig= when configuring, then FILE is /clixon.xml
- # Provide --sysconfig= when configuring then FILE is /etc/clixon.xml
- # FILE is /usr/local/etc/clixon.xml
+ - Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
+ - Provide --with-configfile=FILE when configuring
+ - Provide --with-sysconfig= when configuring, then FILE is /clixon.xml
+ - Provide --sysconfig= when configuring then FILE is /etc/clixon.xml
+ - FILE is /usr/local/etc/clixon.xml
## Can I run Clixon as docker containers?
Yes, the example works as docker containers as well. backend and cli needs a
diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h
index 22ef53b5..f102de3d 100644
--- a/lib/clixon/clixon_plugin.h
+++ b/lib/clixon/clixon_plugin.h
@@ -73,12 +73,16 @@ typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */
#define PLUGIN_EXIT "plugin_exit"
typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
-/*! Called by restconf
- * Returns 0 if credentials OK, -1 if failed
+/*! Called by restconf to ceck credentials and return username
*/
#define PLUGIN_CREDENTIALS "plugin_credentials"
+
/* Plugin credentials
- * username should be freed after use
+ * @param[in] Clicon handle
+ * @param[in] void*, eg Fastcgihandle request restconf
+ * @param[out] username should be freed after use
+ * @retval 0 if credentials OK
+ * @retval -1 credentials not OK
*/
typedef int (plgcredentials_t)(clicon_handle, void *, char **username);
diff --git a/test/lib.sh b/test/lib.sh
index 61dc3c84..b6964e2e 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -50,12 +50,14 @@ new(){
expectfn(){
cmd=$1
expect=$2
+
if [ $# = 3 ]; then
expect2=$3
else
expect2=
fi
- ret=`$cmd`
+ ret=$($cmd)
+
# if [ $? -ne 0 ]; then
# err "wrong args"
# fi
@@ -84,6 +86,7 @@ expectfn(){
expecteq2(){
ret=$1
expect=$2
+
# Match if both are empty string
if [ -z "$ret" -a -z "$expect" ]; then
return
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index c6b22734..7562a8f4 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -81,17 +81,20 @@ new "restconf tests"
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expecteq2 "$(curl -s -X GET http://localhost/.well-known/host-meta)" "
-"
+
"
new "restconf get restconf resource. RFC 8040 3.3 (json)"
-expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}'
+expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}
+
'
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
# Get XML instead of JSON?
-expecteq2 $(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) '2016-06-21'
+expecteq2 "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21
+
'
-new "restconf get restconf/operations. RFC8040 3.3.2"
-expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}'
+new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
+expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt: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)
@@ -120,10 +123,12 @@ expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf empty rpc"
-expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}'
+expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}
+
'
new "restconf get empty config + state json"
-expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}'
+expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
+
'
new "restconf get empty config + state xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data)
@@ -134,7 +139,8 @@ if [ -z "$match" ]; then
fi
new "restconf get data/interfaces-state/interface=eth0 json"
-expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}'
+expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}
+
'
new "restconf get state operation eth0 xml"
# Cant get shell macros to work, inline matching from lib.sh
@@ -146,7 +152,8 @@ if [ -z "$match" ]; then
fi
new "restconf get state operation eth0 type json"
-expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}'
+expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}
+
'
new "restconf get state operation eth0 type xml"
# Cant get shell macros to work, inline matching from lib.sh
@@ -158,7 +165,8 @@ if [ -z "$match" ]; then
fi
new "restconf GET datastore"
-expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}'
+expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
+
'
# Exact match
new "restconf Add subtree to datastore using POST"
@@ -171,8 +179,8 @@ expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type"
#expecteq2 "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
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}\]}}
-$'
+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}\]}}
+
'
new "restconf delete interfaces"
expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
@@ -186,10 +194,11 @@ expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enable
#expecteq2 "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" ""
new "restconf Check eth/0/0 added"
-expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}'
+expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
+
'
new "restconf Re-post eth/0/0 which should generate error"
-expecteq 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
+expecteq 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}
'
new "Add leaf description using POST"
expecteq 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
@@ -198,7 +207,8 @@ new "Add nothing using POST"
expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "data is in some way badly formed"
new "restconf Check description added"
-expecteq "curl -s -G http://localhost/restconf/data" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}'
+expecteq "curl -s -G http://localhost/restconf/data" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
+
'
new "restconf delete eth/0/0"
expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
@@ -207,16 +217,18 @@ new "Check deleted eth/0/0"
expectfn 'curl -s -G http://localhost/restconf/data' $state
new "restconf Re-Delete eth/0/0 using none should generate error"
-expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}}'
+expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}}
'
new "restconf Add subtree eth/0/0 using PUT"
expecteq 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new "restconf get subtree"
-expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}'
+expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
+
'
new "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/rt:fib-route' '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
+
'
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)
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index 70e84bfa..37bb8708 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -84,10 +84,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"
-expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
+expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}
'
new "restconf POST from top"
-expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
+expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}
'
new "restconf DELETE"
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' ""