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.
The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions:
- object-references
- if-feature
- unique
- conformance: feature, if-feature, deviation
- identy, base, identityref
- 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){
xoutput=xpath_first(*xret, "/");
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;
if (xml_apply(xoutput, CX_ELMNT,
(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, "HTTP_COOKIE", 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;
}

View file

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

View file

@ -161,8 +161,13 @@ api_data_get_gen(clicon_handle h,
cbuf *cbj = NULL;;
int code;
const char *reason_phrase;
char *media_accept;
int use_xml = 0; /* By default use JSON */
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);
if ((path = cbuf_new()) == NULL)
goto done;
@ -213,15 +218,22 @@ api_data_get_gen(clicon_handle h,
if ((cbx = cbuf_new()) == NULL)
goto done;
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");
if (head)
goto ok;
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));
if (use_xml){
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));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
@ -276,8 +288,8 @@ api_data_head(clicon_handle h,
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang.data+xml
* Content-Type: application/yang.data+json
* Content-Type: application/yang-data+xml
* Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* 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_spec *yspec;
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\"",
__FUNCTION__,
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){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@ -356,8 +373,14 @@ api_data_post(clicon_handle h,
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
goto done;
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
/* Parse input data as json or xml into xml */
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);
goto done;
}
@ -448,10 +471,15 @@ api_data_put(clicon_handle h,
yang_node *y = NULL;
yang_spec *yspec;
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\"",
__FUNCTION__,
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){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@ -464,8 +492,14 @@ api_data_put(clicon_handle h,
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
goto done;
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
/* Parse input data as json or xml into xml */
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);
goto done;
}
@ -642,6 +676,7 @@ api_operation_post(clicon_handle h,
yang_stmt *yrpc = NULL;
yang_spec *yspec;
yang_stmt *yinput;
yang_stmt *youtput;
cxobj *xdata = NULL;
cxobj *xret = NULL;
cbuf *cbx = NULL;
@ -649,10 +684,23 @@ api_operation_post(clicon_handle h,
cxobj *xbot = NULL;
yang_node *y = NULL;
cxobj *xinput;
cxobj *xoutput;
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);
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){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@ -677,8 +725,14 @@ api_operation_post(clicon_handle h,
if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0)
goto done;
if (data){
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
/* Parse input data as json or xml into xml */
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);
goto done;
}
@ -705,22 +759,44 @@ api_operation_post(clicon_handle h,
goto done;
if ((cbx = cbuf_new()) == NULL)
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 */
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");
vec = xml_childvec_get(xret);
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
if (xoutput){
if (use_xml){
if (clicon_xml2cbuf(cbx, xoutput, 0, 1) < 0)
goto done;
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
}
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;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (xtop)
xml_free(xtop);
if (xret)
xml_free(xret);
if (cbx)

View file

@ -412,7 +412,7 @@ xml2json1_cbuf(cbuf *cb,
* goto err;
* cbuf_free(cb);
* @endcode
* See also xml2json
* @see clicon_xml2cbuf
*/
int
xml2json_cbuf(cbuf *cb,
@ -442,7 +442,7 @@ xml2json_cbuf(cbuf *cb,
/*! 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,
* 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[in] vec Vector of xml objecst
* @param[in] veclen Length of vector

View file

@ -40,7 +40,7 @@ expectfn(){
return
fi
# grep extended grep
match=`echo "$ret" | grep -EZo "$expect"`
match=`echo $ret | grep -EZo "$expect"`
# echo "ret:\"$ret\""
# echo "expect:\"$expect\""
# 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"
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"
expectfn "curl -sSG http://localhost/restconf/data" "^null $"
expectfn "curl -sSG http://localhost/restconf/data" "null"
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' ""
@ -46,7 +47,7 @@ new "restconf delete interfaces"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
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"
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' ""
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"
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"}}}
$'
new "restconf rpc using POST (wrong output encoding)"
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"}}}} $'
new "restconf rpc using POST json"
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"
sudo pkill -u www-data clixon_restconf