New configuration option: CLICON_RESTCONF_PRETTY
Changed RESTCONF GET to return object referenced. ie, GET /restconf/data/X returns X.
This commit is contained in:
parent
cefaf4717f
commit
363bd5d19d
7 changed files with 169 additions and 102 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: <get-config>, <get>
|
||||
*/
|
||||
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; i<xlen; i++){
|
||||
x = xvec[i];
|
||||
#if 1 /* DEBUG */
|
||||
{
|
||||
cbuf *cb = cbuf_new();
|
||||
clicon_xml2cbuf(cb, x, 0, 0);
|
||||
clicon_debug(1, "%s x:%s", __FUNCTION__, cbuf_get(cb));
|
||||
cbuf_free(cb);
|
||||
}
|
||||
#endif
|
||||
if (use_xml){
|
||||
if (clicon_xml2cbuf(cbx, x, 0, pretty) < 0) /* Dont print top object? */
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
if (xml2json_cbuf(cbx, x, pretty) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
|
||||
FCGX_FPrintF(r->out, "%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
|
||||
|
|
|
|||
|
|
@ -549,6 +549,7 @@ match_base_child(cxobj *x0,
|
|||
char **keyval = NULL;
|
||||
char **keyvec = NULL;
|
||||
int i;
|
||||
int yorder;
|
||||
|
||||
*x0cp = NULL; /* return value */
|
||||
switch (yc->ys_keyword){
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue