diff --git a/README.md b/README.md index e15be22b..2b1f5094 100644 --- a/README.md +++ b/README.md @@ -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. + diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index 49847e50..d5b3f427 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -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) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 1f0073f7..80571eac 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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; } diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 2f19b221..97cf3f36 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -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); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 35299ebf..e2d7412f 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -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 (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) - goto done; + 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) - 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"); - + if (xoutput){ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xoutput, 0, 1) < 0) + goto done; + } + 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) diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 8d8168cd..c2c0934a 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -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 diff --git a/test/lib.sh b/test/lib.sh index 88c60af6..7194396d 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -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\"" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index b9e9e236..9bad8f9f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -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=" ipv4 2.3.4.5 " +match=`echo $ret | grep -EZo "$expect"` +echo -n "ret: " +echo $ret new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf