diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b58a9e5..6d7a1eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ * Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed. * Clixon XML C-lib prior to 3.4.0. As enabled with `configure --with-xml-compat` +* new configuration option: CLICON_RESTCONF_PRETTY +* Changed RESTCONF GET to return object referenced. ie, GET /restconf/data/X returns X. + ### Corrected Bugs * Corrected "No yang spec" printed on tty on leafref CLI usage diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 10bfd062..0948f46b 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -88,7 +88,6 @@ * @param[out] commands vector of function pointers to callback functions * @param[out] helptxt vector of pointers to helptexts * @see cli_expand_var_generate This is where arg is generated - * XXX: helptexts? */ int expand_dbvar(void *h, diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 807483d3..a7952a9c 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -9,6 +9,8 @@ There is currently (2017) a [RFC 8040: RESTCONF Protocol](https://tools.ietf.org including: - query parameters (section 4.9) - notifications (sec 6) +- GET /restconf/ (sec 3.3) +- GET /restconf/yang-library-version (sec 3.3.3) - only rudimentary error reporting exists (sec 7) ### Installation using Nginx diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 21331273..549cda02 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -140,82 +140,139 @@ api_data_options(clicon_handle h, return 0; } -/*! Generic GET (both HEAD and GET) +/*! Return error on get/head request + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] xerr XML error message from backend */ static int -api_data_get_gen(clicon_handle h, +api_data_get_err(clicon_handle h, FCGX_Request *r, - cvec *pcvec, - int pi, - cvec *qvec, - int head) + cxobj *xerr) { int retval = -1; - cbuf *path = NULL; + cbuf *cbj = NULL; + cxobj *xtag; + int code; + const char *reason_phrase; + + if ((cbj = cbuf_new()) == NULL) + goto done; + if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ + notfound(r); /* bad reply? */ + goto done; + } + 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; + 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"); + retval = 0; + done: + if (cbj) + cbuf_free(cbj); + return retval; +} + +/*! Generic GET (both HEAD and GET) + * According to restconf + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] head If 1 is HEAD, otherwise GET + * @code + * curl -G http://localhost/restconf/data/interfaces/interface=eth0 + * @endcode + * 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 + * 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 + * 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 + * "400 Bad Request" status-line MUST be returned by the server. + * Netconf: , + */ +static int +api_data_get2(clicon_handle h, + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec, + int head) +{ + int retval = -1; + cbuf *cbpath = NULL; + char *path; cbuf *cbx = NULL; - cxobj **vec = NULL; yang_spec *yspec; cxobj *xret = NULL; cxobj *xerr; - cxobj *xtag; - cbuf *cbj = NULL;; - int code; - const char *reason_phrase; char *media_accept; int use_xml = 0; /* By default use JSON */ + cxobj **xvec = NULL; + size_t xlen; + int pretty; + int i; + cxobj *x; clicon_debug(1, "%s", __FUNCTION__); + 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++; yspec = clicon_dbspec_yang(h); - if ((path = cbuf_new()) == NULL) + if ((cbpath = cbuf_new()) == NULL) goto done; - cprintf(path, "/"); - if (api_path2xpath_cvv(yspec, pcvec, pi, path) < 0){ + cprintf(cbpath, "/"); + clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); + /* We know "data" is element pi-1 */ + if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ notfound(r); goto done; } - clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path)); - if (clicon_rpc_get(h, cbuf_get(path), &xret) < 0){ + path = cbuf_get(cbpath); + clicon_debug(1, "%s path:%s", __FUNCTION__, path); + if (clicon_rpc_get(h, path, &xret) < 0){ notfound(r); goto done; } -#if 0 /* DEBUG */ + /* We get return via netconf which is complete tree from root + * We need to cut that tree to only the object. + */ +#if 1 /* DEBUG */ { cbuf *cb = cbuf_new(); - xml2json_cbuf(cb, xret, 1); + clicon_xml2cbuf(cb, xret, 0, 0); clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); cbuf_free(cb); } #endif + /* Check if error return */ if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ - if ((cbj = cbuf_new()) == NULL) + if (api_data_get_err(h, r, xerr) < 0) goto done; - if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ - notfound(r); /* bad reply? */ - goto done; - } - 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; - 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"); goto ok; } + /* Normal return, no error */ if ((cbx = cbuf_new()) == NULL) goto done; FCGX_SetExitStatus(200, r->out); /* OK */ @@ -223,17 +280,39 @@ api_data_get_gen(clicon_handle h, 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)); - - 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; + if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */ + goto done; + } + else{ + if (xml2json_cbuf(cbx, xret, pretty) < 0) + goto done; + } } else{ - vec = xml_childvec_get(xret); - if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) + if (xpath_vec(xret, path, &xvec, &xlen) < 0) goto done; + clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, xlen); + for (i=0; iout, "%s", cbx?cbuf_get(cbx):""); @@ -244,12 +323,12 @@ api_data_get_gen(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (cbx) cbuf_free(cbx); - if (cbj) - cbuf_free(cbj); - if (path) - cbuf_free(path); + if (cbpath) + cbuf_free(cbpath); if (xret) xml_free(xret); + if (xvec) + free(xvec); return retval; } @@ -271,7 +350,7 @@ api_data_head(clicon_handle h, int pi, cvec *qvec) { - return api_data_get_gen(h, r, pcvec, pi, qvec, 1); + return api_data_get2(h, r, pcvec, pi, qvec, 1); } /*! REST GET method @@ -304,7 +383,7 @@ api_data_get(clicon_handle h, int pi, cvec *qvec) { - return api_data_get_gen(h, r, pcvec, pi, qvec, 0); + return api_data_get2(h, r, pcvec, pi, qvec, 0); } /*! REST POST method diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index ef7e27c8..ed2e987e 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -549,7 +549,8 @@ match_base_child(cxobj *x0, char **keyval = NULL; char **keyvec = NULL; int i; - + int yorder; + *x0cp = NULL; /* return value */ switch (yc->ys_keyword){ case Y_CONTAINER: /* Equal regardless */ @@ -591,51 +592,21 @@ match_base_child(cxobj *x0, break; } /* Get match */ - { - int yorder; - - /* XXX: No we cant do this. on uppermost layer it can look like this: - * config - * ximport-----ymod = ietf - * interfaces--ymod = example - * Which means yang order can be different for different children in the - * same childvec. - */ - if (xml_child_sort==0) - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); + if (xml_child_sort==0) + *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); + else{ + if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ + yorder = yang_order(yc); + *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); + } else{ -#if 1 - if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ - yorder = yang_order(yc); - *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); - } - else{ #if 1 /* This is just a warning, but a catcher for when xml tree is not - populated with yang spec. If you see this, a previous inovation of, + populated with yang spec. If you see this, a previous invacation of, for example xml_spec_populate() may be missing */ - clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); + clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); #endif - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); - } -#else - cxobj *xx; - - - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); - if (xml_child_nr(x0) && xml_spec(xml_child_i(x0,0))!=NULL){ - xx = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); - if (xx!=*x0cp){ - clicon_log(LOG_WARNING, "%s mismatch", __FUNCTION__); - fprintf(stderr, "mismatch\n"); - xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); - assert(0); - } - } - else - clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); -#endif } } ok: diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index a44f28a1..22be2b63 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -1046,10 +1046,10 @@ xpath_each(cxobj *xcur, */ int xpath_vec(cxobj *xcur, - char *format, - cxobj ***vec, - size_t *veclen, - ...) + char *format, + cxobj ***vec, + size_t *veclen, + ...) { int retval = -1; va_list ap; diff --git a/yang/clixon-config@2017-12-27.yang b/yang/clixon-config@2017-12-27.yang index 1bbc1dcb..d9190da7 100644 --- a/yang/clixon-config@2017-12-27.yang +++ b/yang/clixon-config@2017-12-27.yang @@ -11,7 +11,7 @@ module clixon-config { description "Clixon configuration file ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren This file is part of CLIXON @@ -130,6 +130,19 @@ module clixon-config { "FastCGI unix socket. Should be specified in webserver Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock"; } + leaf CLICON_RESTCONF_PRETTY { + type boolean; + default true; + description + "Restconf return value pretty print. + Restconf clients may add HTTP header: + Accept: application/yang-data+json, or + Accept: application/yang-data+xml + to get return value in XML or JSON. + RFC 8040 examples print XML and JSON in pretty-printed form. + Setting this value to false makes restconf return not pretty-printed + which may be desirable for performance or tests"; + } leaf CLICON_CLI_DIR { type string; description