This commit is contained in:
Olof hagsand 2017-08-01 19:39:19 +02:00
parent fd91bb2933
commit 265ed859de
8 changed files with 137 additions and 39 deletions

View file

@ -88,9 +88,14 @@ used to generate an interactive CLI client as well as provide
[Restconf](apps/restconf/README.md) clients. [Restconf](apps/restconf/README.md) clients.
The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions: The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions:
- object-references - conformance: feature, if-feature, deviation
- if-feature - identy, base, identityref
- unique - list features: min/max-elements, unique, ordered-by
There are also new features in YANG 1.1 [YANG RFC
7950](https://www.rfc-editor.org/rfc/rfc7950.txt) most of which are
not implemented.

View file

@ -269,7 +269,7 @@ netconf_plugin_callbacks(clicon_handle h,
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){
xoutput=xpath_first(*xret, "/"); xoutput=xpath_first(*xret, "/");
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yinput) < 0) if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
goto done; goto done;
if (xml_apply(xoutput, CX_ELMNT, if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)

View file

@ -264,7 +264,19 @@ test(FCGX_Request *r,
printparam(r, "SERVER_NAME", dbg); printparam(r, "SERVER_NAME", dbg);
printparam(r, "HTTP_COOKIE", dbg); printparam(r, "HTTP_COOKIE", dbg);
printparam(r, "HTTPS", dbg); printparam(r, "HTTPS", dbg);
printparam(r, "HTTP_ACCEPT", dbg);
printparam(r, "HTTP_CONTENT_TYPE", dbg);
#if 0 /* For debug */
clicon_debug(1, "All environment vars:");
{
extern char **environ;
int i;
for (i = 0; environ[i] != NULL; i++){
clicon_debug(1, "%s", environ[i]);
}
}
clicon_debug(1, "End environment vars:");
#endif
return 0; return 0;
} }

View file

@ -151,7 +151,6 @@ api_operations(clicon_handle h,
return retval; return retval;
} }
/*! Process a FastCGI request /*! Process a FastCGI request
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
*/ */
@ -189,12 +188,10 @@ request_process(clicon_handle h,
clicon_debug(1, "DATA=%s", data); clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0) if (str2cvec(data, '&', '=', &dvec) < 0)
goto done; goto done;
if ((method = pvec[2]) == NULL){ if ((method = pvec[2]) == NULL){
retval = notfound(r); retval = notfound(r);
goto done; goto done;
} }
retval = 0; retval = 0;
test(r, 1); test(r, 1);
/* If present, check credentials */ /* If present, check credentials */
@ -392,7 +389,7 @@ main(int argc,
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){ if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 || if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0) strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
request_process(h, r); request_process(h, r); /* This is the function */
else{ else{
clicon_debug(1, "top-level not found"); clicon_debug(1, "top-level not found");
notfound(r); notfound(r);

View file

@ -161,8 +161,13 @@ api_data_get_gen(clicon_handle h,
cbuf *cbj = NULL;; cbuf *cbj = NULL;;
int code; int code;
const char *reason_phrase; const char *reason_phrase;
char *media_accept;
int use_xml = 0; /* By default use JSON */
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL) if ((path = cbuf_new()) == NULL)
goto done; goto done;
@ -213,15 +218,22 @@ api_data_get_gen(clicon_handle h,
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
goto done; goto done;
FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
if (head) if (head)
goto ok; goto ok;
clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret)); clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret));
vec = xml_childvec_get(xret);
clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret)); clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret));
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) if (use_xml){
goto done; if (clicon_xml2cbuf(cbx, xret, 0, 1) < 0) /* Dont print top object? */
goto done;
}
else{
vec = xml_childvec_get(xret);
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
goto done;
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n"); FCGX_FPrintF(r->out, "\r\n\r\n");
@ -276,8 +288,8 @@ api_data_head(clicon_handle h,
* Request may contain * Request may contain
* Accept: application/yang.data+json,application/yang.data+xml * Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of: * Response contains one of:
* Content-Type: application/yang.data+xml * Content-Type: application/yang-data+xml
* Content-Type: application/yang.data+json * Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf- * NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML * list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a * encoding is used in the response, then an error response containing a
@ -340,10 +352,15 @@ api_data_post(clicon_handle h,
yang_node *y = NULL; yang_node *y = NULL;
yang_spec *yspec; yang_spec *yspec;
cxobj *xa; cxobj *xa;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__, __FUNCTION__,
api_path, data); api_path, data);
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
@ -356,8 +373,14 @@ api_data_post(clicon_handle h,
xbot = xtop; xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
goto done; goto done;
/* Parse input data as json into xml */ /* Parse input data as json or xml into xml */
if (json_parse_str(data, &xdata) < 0){ if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done; goto done;
} }
@ -448,10 +471,15 @@ api_data_put(clicon_handle h,
yang_node *y = NULL; yang_node *y = NULL;
yang_spec *yspec; yang_spec *yspec;
cxobj *xa; cxobj *xa;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__, __FUNCTION__,
api_path, data); api_path, data);
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
@ -464,8 +492,14 @@ api_data_put(clicon_handle h,
xbot = xtop; xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
goto done; goto done;
/* Parse input data as json into xml */ /* Parse input data as json or xml into xml */
if (json_parse_str(data, &xdata) < 0){ if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done; goto done;
} }
@ -642,6 +676,7 @@ api_operation_post(clicon_handle h,
yang_stmt *yrpc = NULL; yang_stmt *yrpc = NULL;
yang_spec *yspec; yang_spec *yspec;
yang_stmt *yinput; yang_stmt *yinput;
yang_stmt *youtput;
cxobj *xdata = NULL; cxobj *xdata = NULL;
cxobj *xret = NULL; cxobj *xret = NULL;
cbuf *cbx = NULL; cbuf *cbx = NULL;
@ -649,10 +684,23 @@ api_operation_post(clicon_handle h,
cxobj *xbot = NULL; cxobj *xbot = NULL;
yang_node *y = NULL; yang_node *y = NULL;
cxobj *xinput; cxobj *xinput;
cxobj *xoutput;
cxobj *x; cxobj *x;
cxobj **vec = NULL; char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
char *media_accept;
int use_xml = 0; /* By default return JSON */
clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data); clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data);
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 (strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"",
__FUNCTION__, media_accept, media_content_type);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
@ -677,8 +725,14 @@ api_operation_post(clicon_handle h,
if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0)
goto done; goto done;
if (data){ if (data){
/* Parse input data as json into xml */ /* Parse input data as json or xml into xml */
if (json_parse_str(data, &xdata) < 0){ if (parse_xml){
if (clicon_xml_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
}
else if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done; goto done;
} }
@ -705,22 +759,44 @@ api_operation_post(clicon_handle h,
goto done; goto done;
if ((cbx = cbuf_new()) == NULL) if ((cbx = cbuf_new()) == NULL)
goto done; goto done;
xoutput=xpath_first(xret, "/");
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL &&
xoutput){
xml_name_set(xoutput, "output");
clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx));
cbuf_reset(cbx);
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
goto done;
if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done;
if (xml_yang_validate_add(xoutput, NULL) < 0)
goto done;
}
/* Sanity check of outgoing XML */ /* Sanity check of outgoing XML */
FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
vec = xml_childvec_get(xret); if (xoutput){
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) if (use_xml){
goto done; if (clicon_xml2cbuf(cbx, xoutput, 0, 1) < 0)
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); goto done;
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); }
FCGX_FPrintF(r->out, "\r\n\r\n"); else
if (xml2json_cbuf(cbx, xoutput, 1) < 0)
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, "\r\n\r\n");
}
retval = 0; retval = 0;
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata) if (xdata)
xml_free(xdata); xml_free(xdata);
if (xtop)
xml_free(xtop);
if (xret) if (xret)
xml_free(xret); xml_free(xret);
if (cbx) if (cbx)

View file

@ -412,7 +412,7 @@ xml2json1_cbuf(cbuf *cb,
* goto err; * goto err;
* cbuf_free(cb); * cbuf_free(cb);
* @endcode * @endcode
* See also xml2json * @see clicon_xml2cbuf
*/ */
int int
xml2json_cbuf(cbuf *cb, xml2json_cbuf(cbuf *cb,
@ -442,7 +442,7 @@ xml2json_cbuf(cbuf *cb,
/*! Translate a vector of xml objects to JSON CLigen buffer. /*! Translate a vector of xml objects to JSON CLigen buffer.
* This is done by adding a top pseudo-object, and add the vector as subs, * This is done by adding a top pseudo-object, and add the vector as subs,
* and then not pritning the top pseudo-.object using the 'flat' option. * and then not printing the top pseudo-object using the 'flat' option.
* @param[out] cb Cligen buffer to write to * @param[out] cb Cligen buffer to write to
* @param[in] vec Vector of xml objecst * @param[in] vec Vector of xml objecst
* @param[in] veclen Length of vector * @param[in] veclen Length of vector

View file

@ -40,7 +40,7 @@ expectfn(){
return return
fi fi
# grep extended grep # grep extended grep
match=`echo "$ret" | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
# echo "ret:\"$ret\"" # echo "ret:\"$ret\""
# echo "expect:\"$expect\"" # echo "expect:\"$expect\""
# echo "match:\"$match\"" # echo "match:\"$match\""

View file

@ -30,10 +30,11 @@ new "restconf options"
expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head" new "restconf head"
expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json" expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf get empty config" new "restconf get empty config"
expectfn "curl -sSG http://localhost/restconf/data" "^null $" expectfn "curl -sSG http://localhost/restconf/data" "null"
new "restconf Add subtree to datastore using POST" new "restconf Add subtree to datastore using POST"
expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
@ -46,7 +47,7 @@ new "restconf delete interfaces"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' "" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
new "restconf Check empty config" new "restconf Check empty config"
expectfn "curl -sSG http://localhost/restconf/data" "^null $" expectfn "curl -sSG http://localhost/restconf/data" "null"
new "restconf Add interfaces subtree eth/0/0 using POST" new "restconf Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' ""
@ -69,7 +70,7 @@ new "restconf delete eth/0/0"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new "Check deleted eth/0/0" new "Check deleted eth/0/0"
expectfn 'curl -sS -G http://localhost/restconf/data' "^null $" expectfn 'curl -sS -G http://localhost/restconf/data' "null"
new "restconf Re-Delete eth/0/0 using none should generate error" new "restconf Re-Delete eth/0/0 using none should generate error"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found"
@ -81,8 +82,15 @@ new "restconf get subtree"
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$' $'
new "restconf rpc using POST (wrong output encoding)" new "restconf rpc using POST json"
expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"rpc-reply": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} $' expectfn 'curl -sS -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 -sS -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)
expect="<output> <route> <address-family>ipv4</address-family> <next-hop> <next-hop-list>2.3.4.5</next-hop-li t> </next-hop> </route> </output> "
match=`echo $ret | grep -EZo "$expect"`
echo -n "ret: "
echo $ret
new "Kill restconf daemon" new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf