* Changed so that 400 Bad Request are for invalid api-path or unknown yang elements, 404 Not Found for valid xml when object not found.

This commit is contained in:
Olof hagsand 2019-10-07 22:20:34 +02:00
parent 6e41592aec
commit 8cdb0bb062
13 changed files with 189 additions and 107 deletions

View file

@ -29,6 +29,7 @@
See function reference how to make a call. See function reference how to make a call.
* RESTCONF error reporting * RESTCONF error reporting
* Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 Bad request invalid-value, or unknown-element. * Invalid api-path syntax (eg non-matching yang) error changed from 412 operation-failed to 400 Bad request invalid-value, or unknown-element.
* Changed so that `400 Bad Request` are for invalid api-path or unknown yang elements, `404 Not Found` for valid xml when object not found.
* Typical installation should now add a `clicon` user (as well as group) * Typical installation should now add a `clicon` user (as well as group)
* New clixon-config@2019-09-11.yang revision * New clixon-config@2019-09-11.yang revision
* Added: CLICON_BACKEND_USER: drop of privileges to user, * Added: CLICON_BACKEND_USER: drop of privileges to user,

View file

@ -245,6 +245,8 @@ cli_dbxml(clicon_handle h,
yang_stmt *y = NULL; /* yang spec of xpath */ yang_stmt *y = NULL; /* yang spec of xpath */
cxobj *xtop = NULL; /* xpath root */ cxobj *xtop = NULL; /* xpath root */
cxobj *xa; /* attribute */ cxobj *xa; /* attribute */
cxobj *xerr = NULL;
int ret;
if (cvec_len(argv) != 1){ if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string"); clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string");
@ -262,8 +264,14 @@ cli_dbxml(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL) if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done; goto done;
xbot = xtop; xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y) < 1) if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){
clicon_rpc_generate_error("Modify datastore", xerr);
goto done;
}
}
if ((xa = xml_new("operation", xbot, NULL)) == NULL) if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done; goto done;
xml_type_set(xa, CX_ATTR); xml_type_set(xa, CX_ATTR);
@ -289,6 +297,8 @@ cli_dbxml(clicon_handle h,
} }
retval = 0; retval = 0;
done: done:
if (xerr)
xml_free(xerr);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (api_path) if (api_path)
@ -683,7 +693,7 @@ compare_dbs(clicon_handle h,
{ {
cxobj *xc1 = NULL; /* running xml */ cxobj *xc1 = NULL; /* running xml */
cxobj *xc2 = NULL; /* candidate xml */ cxobj *xc2 = NULL; /* candidate xml */
cxobj *xerr; cxobj *xerr = NULL;
int retval = -1; int retval = -1;
int astext; int astext;
@ -715,7 +725,6 @@ compare_dbs(clicon_handle h,
xml_free(xc1); xml_free(xc1);
if (xc2) if (xc2)
xml_free(xc2); xml_free(xc2);
return retval; return retval;
} }

View file

@ -100,7 +100,8 @@ expand_dbvar(void *h,
cxobj *xt = NULL; cxobj *xt = NULL;
char *xpath = NULL; char *xpath = NULL;
cxobj **xvec = NULL; cxobj **xvec = NULL;
cxobj *xerr; cxobj *xe; /* direct ptr */
cxobj *xerr = NULL; /* free */
size_t xlen = 0; size_t xlen = 0;
cxobj *x; cxobj *x;
char *bodystr; char *bodystr;
@ -118,6 +119,7 @@ expand_dbvar(void *h,
char *xpathcur; char *xpathcur;
char *reason = NULL; char *reason = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
int ret;
if (argv == NULL || cvec_len(argv) != 2){ if (argv == NULL || cvec_len(argv) != 2){
clicon_err(OE_PLUGIN, 0, "requires arguments: <db> <xmlkeyfmt>"); clicon_err(OE_PLUGIN, 0, "requires arguments: <db> <xmlkeyfmt>");
@ -155,8 +157,8 @@ expand_dbvar(void *h,
/* Get configuration */ /* Get configuration */
if (clicon_rpc_get_config(h, NULL, dbstr, xpath, nsc, &xt) < 0) /* XXX */ if (clicon_rpc_get_config(h, NULL, dbstr, xpath, nsc, &xt) < 0) /* XXX */
goto done; goto done;
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ if ((xe = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error("Get configuration", xerr); clicon_rpc_generate_error("Get configuration", xe);
goto ok; goto ok;
} }
xcur = xt; /* default top-of-tree */ xcur = xt; /* default top-of-tree */
@ -169,8 +171,14 @@ expand_dbvar(void *h,
* xpath2xml would have worked!! * xpath2xml would have worked!!
* XXX: but y is just the first in this list, there could be other y:s? * XXX: but y is just the first in this list, there could be other y:s?
*/ */
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y) < 1) if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 0, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){
clicon_rpc_generate_error("Expand datastore symbol", xerr);
goto done;
}
}
if (y==NULL) if (y==NULL)
goto ok; goto ok;
@ -242,6 +250,8 @@ expand_dbvar(void *h,
ok: ok:
retval = 0; retval = 0;
done: done:
if (xerr)
xml_free(xerr);
if (nsc) if (nsc)
xml_nsctx_free(nsc); xml_nsctx_free(nsc);
if (reason) if (reason)

View file

@ -210,3 +210,8 @@ but you need to ensure /www-data/fastcgi_restconf.sock has the following access:
``` ```
rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock
``` ```
You can set debug level of the backend via restconf:
```
url -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-lib:input":{"level":1}}' http://localhost/restconf/operations/clixon-lib:debug
```

View file

@ -338,14 +338,9 @@ api_data_write(clicon_handle h,
/* Translate api_path to xml in the form of xtop/xbot */ /* Translate api_path to xml in the form of xtop/xbot */
xbot = xtop; xbot = xtop;
if (api_path){ /* If URI, otherwise top data/config object */ if (api_path){ /* If URI, otherwise top data/config object */
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0) if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done; goto done;
if (ybot)
ymodapi = ys_module(ybot);
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
@ -354,6 +349,8 @@ api_data_write(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (ybot)
ymodapi = ys_module(ybot);
} }
/* 4.4.1: The message-body MUST contain exactly one instance of the /* 4.4.1: The message-body MUST contain exactly one instance of the
* expected data resource. (tested again below) * expected data resource. (tested again below)
@ -841,12 +838,9 @@ api_data_delete(clicon_handle h,
goto done; goto done;
xbot = xtop; xbot = xtop;
if (api_path){ if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y)) < 0) if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;

View file

@ -172,7 +172,6 @@ api_data_get2(clicon_handle h,
if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &nsc, &xerr)) < 0) if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &nsc, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;

View file

@ -119,6 +119,7 @@ api_data_post(clicon_handle h,
yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */ yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
yang_stmt *ymoddata = NULL; /* yang module of data (-d) */ yang_stmt *ymoddata = NULL; /* yang module of data (-d) */
yang_stmt *yspec; yang_stmt *yspec;
yang_stmt *ydata;
cxobj *xa; cxobj *xa;
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xretcom = NULL; /* return from commit */ cxobj *xretcom = NULL; /* return from commit */
@ -144,14 +145,9 @@ api_data_post(clicon_handle h,
/* Translate api_path to xtop/xbot */ /* Translate api_path to xtop/xbot */
xbot = xtop; xbot = xtop;
if (api_path){ if (api_path){
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot)) < 0) if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &ybot, &xerr)) < 0)
goto done; goto done;
if (ybot)
ymodapi = ys_module(ybot);
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;
@ -160,6 +156,8 @@ api_data_post(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (ybot)
ymodapi = ys_module(ybot);
} }
#if 1 #if 1
if (debug){ if (debug){
@ -250,30 +248,9 @@ api_data_post(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
xdata = xml_child_i(xdata0,0); xdata = xml_child_i(xdata0, 0);
/* If the api-path (above) defines a module, then xdata must have a prefix
* and it match the module defined in api-path.
* In a POST, maybe there are cornercases where xdata (which is a child) and
* xbot (which is the parent) may have non-matching namespaces?
* This does not apply if api-path is / (no module)
*/
if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0) if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0)
goto done; goto done;
if (ymoddata && ymodapi){
if (ymoddata != ymodapi){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
/* Add operation (create/replace) as attribute */ /* Add operation (create/replace) as attribute */
if ((xa = xml_new("operation", xdata, NULL)) == NULL) if ((xa = xml_new("operation", xdata, NULL)) == NULL)
goto done; goto done;
@ -289,6 +266,34 @@ api_data_post(clicon_handle h,
nullspec = (xml_spec(xdata) == NULL); nullspec = (xml_spec(xdata) == NULL);
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0) if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done; goto done;
/* ybot is parent of spec(parent(data))) */
if (ymoddata && (ydata = xml_spec(xdata)) != NULL){
if (ys_real_module(ydata) != ymoddata){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
/* If URI points out an object, then data's parent should be that object
*/
if (ybot && yang_parent_get(ydata) != ybot){
if (netconf_malformed_message_xml(&xerr, "Data is not prefixed with matching namespace") < 0)
goto done;
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
if (media_in == YANG_DATA_JSON && nullspec){ if (media_in == YANG_DATA_JSON && nullspec){
/* json2xml decode may not have been done above in json_parse, /* json2xml decode may not have been done above in json_parse,
need to be done here instead need to be done here instead
@ -821,12 +826,9 @@ api_operations_post(clicon_handle h,
goto done; goto done;
/* Here xtop is: <rpc username="foo"/> */ /* Here xtop is: <rpc username="foo"/> */
} }
if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, 1, &xbot, &y)) < 0) if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, 1, &xbot, &y, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* validation failed */ if (ret == 0){ /* validation failed */
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
clicon_err_reset();
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done; goto done;

View file

@ -73,7 +73,8 @@ int xml_spec_populate(cxobj *x, void *arg);
int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, cvec **nsc, cxobj **xerr); int api_path2xpath_cvv(cvec *api_path, int offset, yang_stmt *yspec, cbuf *xpath, cvec **nsc, cxobj **xerr);
int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, cvec **nsc); int api_path2xpath(char *api_path, yang_stmt *yspec, char **xpath, cvec **nsc);
int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop, int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp); yang_class nodeclass, int strict,
cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr);
int xml2xpath(cxobj *x, char **xpath); int xml2xpath(cxobj *x, char **xpath);
int xml2api_path_1(cxobj *x, cbuf *cb); int xml2api_path_1(cxobj *x, cbuf *cb);

View file

@ -2467,6 +2467,10 @@ api_path2xpath_cvv(cvec *api_path,
/* Initialize namespace context */ /* Initialize namespace context */
if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL) if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
goto done; goto done;
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
for (i=offset; i<cvec_len(api_path); i++){ for (i=offset; i<cvec_len(api_path); i++){
cv = cvec_i(api_path, i); cv = cvec_i(api_path, i);
nodeid = cv_name_get(cv); nodeid = cv_name_get(cv);
@ -2477,10 +2481,6 @@ api_path2xpath_cvv(cvec *api_path,
__FUNCTION__, i, prefix?prefix:"", name); __FUNCTION__, i, prefix?prefix:"", name);
/* top-node must have prefix */ /* top-node must have prefix */
if (i == offset && prefix == NULL){ if (i == offset && prefix == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "'%s': Expected prefix:name", nodeid); cprintf(cberr, "'%s': Expected prefix:name", nodeid);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done; goto done;
@ -2489,10 +2489,6 @@ api_path2xpath_cvv(cvec *api_path,
ymod = NULL; ymod = NULL;
if (prefix){ /* if prefix -> get module + change namespace */ if (prefix){ /* if prefix -> get module + change namespace */
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){ if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "No such yang module: %s", prefix); cprintf(cberr, "No such yang module: %s", prefix);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0) if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done; goto done;
@ -2514,7 +2510,6 @@ api_path2xpath_cvv(cvec *api_path,
* note different from api-path prefix * note different from api-path prefix
*/ */
if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){ if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
xprefix = yang_find_myprefix(y); xprefix = yang_find_myprefix(y);
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix); clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
/* not found, add it to nsc */ /* not found, add it to nsc */
@ -2595,7 +2590,7 @@ api_path2xpath_cvv(cvec *api_path,
} }
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr != NULL) if (cberr)
cbuf_free(cberr); cbuf_free(cberr);
if (valvec) if (valvec)
free(valvec); free(valvec);
@ -2685,11 +2680,12 @@ api_path2xpath(char *api_path,
* @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xpathp Resulting xml tree * @param[out] xpathp Resulting xml tree
* @param[out] ypathp Yang spec matching xpathp * @param[out] ypathp Yang spec matching xpathp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK * @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called * @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called * @retval -1 Fatal error, clicon_err called
* *
* @note both retval 0 and -1 set clicon_err, but the later is fatal * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @see api_path2xpath For api-path to xml xpath translation * @see api_path2xpath For api-path to xml xpath translation
* @see api_path2xml * @see api_path2xml
*/ */
@ -2701,7 +2697,8 @@ api_path2xml_vec(char **vec,
yang_class nodeclass, yang_class nodeclass,
int strict, int strict,
cxobj **xpathp, cxobj **xpathp,
yang_stmt **ypathp) yang_stmt **ypathp,
cxobj **xerr)
{ {
int retval = -1; int retval = -1;
char *nodeid; char *nodeid;
@ -2722,6 +2719,7 @@ api_path2xml_vec(char **vec,
yang_stmt *ymod; yang_stmt *ymod;
yang_stmt *ykey; yang_stmt *ykey;
char *namespace = NULL; char *namespace = NULL;
cbuf *cberr = NULL;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){ if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
if (xpathp) if (xpathp)
@ -2730,6 +2728,11 @@ api_path2xml_vec(char **vec,
*ypathp = y0; *ypathp = y0;
goto ok; goto ok;
} /* E.g "x=1,2" -> nodeid:x restval=1,2 */ } /* E.g "x=1,2" -> nodeid:x restval=1,2 */
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* restval is RFC 3896 encoded */ /* restval is RFC 3896 encoded */
if ((restval_enc = index(nodeid, '=')) != NULL){ if ((restval_enc = index(nodeid, '=')) != NULL){
*restval_enc = '\0'; *restval_enc = '\0';
@ -2742,11 +2745,15 @@ api_path2xml_vec(char **vec,
goto done; goto done;
if (y0->ys_keyword == Y_SPEC){ /* top-node */ if (y0->ys_keyword == Y_SPEC){ /* top-node */
if (prefix == NULL){ if (prefix == NULL){
clicon_err(OE_XML, EINVAL, "api-path element '%s', expected prefix:name", nodeid); cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){ if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix); cprintf(cberr, "No such yang module prefix");
if (netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
namespace = yang_find_mynamespace(ymod); namespace = yang_find_mynamespace(ymod);
@ -2756,12 +2763,15 @@ api_path2xml_vec(char **vec,
yang_find_schemanode(y0, name): yang_find_schemanode(y0, name):
yang_find_datanode(y0, name); yang_find_datanode(y0, name);
if (y == NULL){ if (y == NULL){
clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name); if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail; goto fail;
} }
if (prefix && namespace == NULL){ if (prefix && namespace == NULL){
if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){ if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix); cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
namespace = yang_find_mynamespace(ymod); namespace = yang_find_mynamespace(ymod);
@ -2789,7 +2799,9 @@ api_path2xml_vec(char **vec,
} }
if (restval==NULL){ if (restval==NULL){
if (strict){ if (strict){
clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
} }
@ -2800,7 +2812,9 @@ api_path2xml_vec(char **vec,
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done; goto done;
if ((nvalvec != cvec_len(cvk)) && strict){ if ((nvalvec != cvec_len(cvk)) && strict){
clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name); cprintf(cberr, "List key %s length mismatch", name);
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
} }
@ -2814,9 +2828,11 @@ api_path2xml_vec(char **vec,
while ((cvi = cvec_each(cvk, cvi)) != NULL){ while ((cvi = cvec_each(cvk, cvi)) != NULL){
keyname = cv_string_get(cvi); keyname = cv_string_get(cvi);
if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){ if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"", cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"",
yang_argument_get(y), keyname); yang_argument_get(y), keyname);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done; goto done;
goto fail;
} }
if ((xn = xml_new(keyname, x, ykey)) == NULL) if ((xn = xml_new(keyname, x, ykey)) == NULL)
goto done; goto done;
@ -2845,11 +2861,14 @@ api_path2xml_vec(char **vec,
if ((retval = api_path2xml_vec(vec+1, nvec-1, if ((retval = api_path2xml_vec(vec+1, nvec-1,
x, y, x, y,
nodeclass, strict, nodeclass, strict,
xpathp, ypathp)) < 1) xpathp, ypathp, xerr)) < 1)
goto done; goto done;
ok: ok:
retval = 1; /* OK */ retval = 1; /* OK */
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
if (prefix) if (prefix)
free(prefix); free(prefix);
if (name) if (name)
@ -2871,10 +2890,11 @@ api_path2xml_vec(char **vec,
* @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] xbotp Resulting xml tree (end of xpath)
* @param[out] ybotp Yang spec matching xbotp * @param[out] ybotp Yang spec matching xbotp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK * @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called * @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called * @retval -1 Fatal error, clicon_err called
* @note both retval 0 and -1 set clicon_err, but the later is fatal * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @example * @example
* api_path: /subif-entry=foo/subid * api_path: /subif-entry=foo/subid
* xtop[in] <config/> * xtop[in] <config/>
@ -2893,17 +2913,24 @@ api_path2xml(char *api_path,
yang_class nodeclass, yang_class nodeclass,
int strict, int strict,
cxobj **xbotp, cxobj **xbotp,
yang_stmt **ybotp) yang_stmt **ybotp,
cxobj **xerr)
{ {
int retval = -1; int retval = -1;
char **vec = NULL; char **vec = NULL;
int nvec; int nvec;
cxobj *xroot; cxobj *xroot;
cbuf *cberr = NULL;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (*api_path!='/'){ if (*api_path!='/'){
clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')", cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path);
api_path); if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
@ -2912,13 +2939,15 @@ api_path2xml(char *api_path,
if (nvec > 1 && !strlen(vec[nvec-1])) if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--; nvec--;
if (nvec < 1){ if (nvec < 1){
clicon_err(OE_XML, EINVAL, "Malformed api-path: %s", api_path); cprintf(cberr, "Malformed api-path: %s: too short)", api_path);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail; goto fail;
} }
nvec--; /* NULL-terminated */ nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec, if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict, xtop, yspec, nodeclass, strict,
xbotp, ybotp)) < 1) xbotp, ybotp, xerr)) < 1)
goto done; goto done;
xml_yang_root(*xbotp, &xroot); xml_yang_root(*xbotp, &xroot);
if (xmlns_assign(xroot) < 0) if (xmlns_assign(xroot) < 0)
@ -2926,6 +2955,8 @@ api_path2xml(char *api_path,
// ok: // ok:
retval = 1; retval = 1;
done: done:
if (cberr)
cbuf_free(cberr);
if (vec) if (vec)
free(vec); free(vec);
return retval; return retval;

View file

@ -85,7 +85,6 @@ module ietf-interfaces {
EOF EOF
# From rfc7950 sec 7.17 # From rfc7950 sec 7.17
# Note "when" is not present
# This is the main module where the augment exists # This is the main module where the augment exists
cat <<EOF > $fyang cat <<EOF > $fyang
module example-augment { module example-augment {
@ -117,7 +116,12 @@ module example-augment {
} }
} }
augment "/if:interfaces/if:interface" { augment "/if:interfaces/if:interface" {
/* when 'derived-from-or-self(if:type, "mymod:some-new-iftype")'; */ when 'derived-from-or-self(if:type, "mymod:some-new-iftype")';
container ospf { /* moved from test_restconf_err (two-level augment) */
leaf reference-bandwidth {
type uint32;
}
}
leaf mandatory-leaf { leaf mandatory-leaf {
mandatory true; mandatory true;
type string; type string;
@ -214,6 +218,38 @@ expectpart "$(curl -s -i -X GET http://localhost/restconf/data/ietf-interfaces:i
new "restconf get augment xml" new "restconf get augment xml"
expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e2</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:other>if:fddi</mymod:other><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e3</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:me>mymod:you</mymod:me><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface></interfaces>' expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' '<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e2</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:other>if:fddi</mymod:other><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface><interface xmlns:mymod="urn:example:augment"><name>e3</name><type>fddi</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><mymod:me>mymod:you</mymod:me><mymod:port>80</mymod:port><mymod:lport>8080</mymod:lport></interface></interfaces>'
#<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
XML=$(cat <<EOF
<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><name>e1</name><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
EOF
)
# Test for multi-module path where an augment stretches across modules
new "restconf PUT augment multi-namespace path e1 (whole path)"
expectpart "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1 -d "$XML")" 0 ''
XML=$(cat <<EOF
<ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf>
EOF
)
new "restconf POST augment multi-namespace path e2 (middle path)"
expectpart "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 ''
new "restconf GET augment multi-namespace top"
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","example-augment:ospf":{"reference-bandwidth":23},"example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}'
new "restconf GET augment multi-namespace level 1"
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","example-augment:ospf":{"reference-bandwidth":23},"example-augment:port":80,"example-augment:lport":8080}\]}'
new "restconf GET augment multi-namespace cross"
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf)" 0 'HTTP/1.1 200 OK' '{"example-augment:ospf":{"reference-bandwidth":23}}'
new "restconf GET augment multi-namespace cross level 2"
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1/example-augment:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example-augment:reference-bandwidth":23}'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -280,7 +280,7 @@ expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypt
# 2. set identity in other module with restconf , read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf
new "restconf add POST instead of PUT (should fail)" new "restconf add POST instead of PUT (should fail)"
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}'
new "restconf add other (des) identity using POST" new "restconf add other (des) identity using POST"
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/example:crypto' expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/example:crypto'

View file

@ -119,6 +119,9 @@ expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"cli
new "restconf empty rpc with extra args (should fail)" new "restconf empty rpc with extra args (should fail)"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}} ' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error"}}} '
new "restconf debug rpc"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
new "restconf get empty config + state json" new "restconf get empty config + state json"
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}} expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
' '
@ -264,7 +267,7 @@ if [ -z "$match" ]; then
fi fi
new "restconf Add subtree without key (expected error)" new "restconf Add subtree without key (expected error)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key, expected '"'"'=restval'"'"'"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
new "restconf Add subtree with too many keys (expected error)" new "restconf Add subtree with too many keys (expected error)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} '

View file

@ -103,13 +103,6 @@ module example{
} }
} }
} }
augment "/aug:route-config/aug:dynamic" {
container ospf {
leaf reference-bandwidth {
type uint32;
}
}
}
} }
EOF EOF
@ -158,11 +151,9 @@ expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a/xxx)" 0 '
new "restconf GET invalid (no yang) element" new "restconf GET invalid (no yang) element"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' expectpart "$(curl -si -X GET http://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}'
if false; then
new "restconf POST non-existent (no yang) element" new "restconf POST non-existent (no yang) element"
# should be invalid element # should be invalid element
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Unknown element: ' expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}'
fi
# Test for multi-module path where an augment stretches across modules # Test for multi-module path where an augment stretches across modules
new "restconf POST augment multi-namespace path" new "restconf POST augment multi-namespace path"
@ -181,8 +172,8 @@ new "restconf GET augment multi-namespace cross level 2"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}' expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}'
# XXX actually no such element # XXX actually no such element
#new "restconf GET augment multi-namespace, no 2nd module in api-path, fail" new "restconf GET augment multi-namespace, no 2nd module in api-path, fail"
#expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}' expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf