RESTCONF PUT list key problems
This commit is contained in:
parent
97079b0a88
commit
dfa3970ab2
6 changed files with 95 additions and 65 deletions
|
|
@ -636,14 +636,14 @@ api_data_post(clicon_handle h,
|
|||
|
||||
/*! Check matching keys
|
||||
*
|
||||
* Check that x1 and x2 (both of type list/leaf-list) share the same key statements
|
||||
* Check that x1 and x2 are of type list/leaf-list and share the same key statements
|
||||
* I.e that if x1=<list><key>b</key></list> then x2 = <list><key>b</key></list> as
|
||||
* well
|
||||
* well. Otherwise return -1.
|
||||
* @param[in] y Yang statement, should be list or leaf-list
|
||||
* @param[in] x1 First XML tree (eg data tree)
|
||||
* @param[in] x2 Second XML tree (eg api-path tree)
|
||||
* @param[in] x1 First XML tree (eg data)
|
||||
* @param[in] x2 Second XML tree (eg api-path)
|
||||
* @retval 0 Yes, keys match
|
||||
* @retval -1 No keys do not match
|
||||
* @retval -1 No, keys do not match
|
||||
* If the target resource represents a YANG leaf-list, then the PUT
|
||||
* method MUST NOT change the value of the leaf-list instance.
|
||||
*
|
||||
|
|
@ -666,7 +666,6 @@ match_list_keys(yang_stmt *y,
|
|||
char *key1;
|
||||
char *key2;
|
||||
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
switch (yang_keyword_get(y)){
|
||||
case Y_LIST:
|
||||
|
|
@ -695,8 +694,9 @@ match_list_keys(yang_stmt *y,
|
|||
goto done; /* keys dont match */
|
||||
break;
|
||||
default:
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
|
|
@ -742,12 +742,12 @@ api_data_put(clicon_handle h,
|
|||
int retval = -1;
|
||||
enum operation_type op = OP_REPLACE;
|
||||
int i;
|
||||
cxobj *xdata = NULL;
|
||||
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
|
||||
cxobj *xdata; /* -d data (without top symbol)*/
|
||||
cbuf *cbx = NULL;
|
||||
cxobj *xtop = NULL; /* xpath root */
|
||||
cxobj *xbot = NULL;
|
||||
cxobj *xparent;
|
||||
cxobj *x;
|
||||
yang_stmt *y = NULL;
|
||||
yang_stmt *yp; /* yang parent */
|
||||
yang_stmt *yspec;
|
||||
|
|
@ -761,6 +761,7 @@ api_data_put(clicon_handle h,
|
|||
char *username;
|
||||
int ret;
|
||||
char *namespace0;
|
||||
char *dname;
|
||||
|
||||
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
|
||||
__FUNCTION__, api_path0, data);
|
||||
|
|
@ -794,7 +795,7 @@ api_data_put(clicon_handle h,
|
|||
}
|
||||
/* Parse input data as json or xml into xml */
|
||||
if (parse_xml){
|
||||
if (xml_parse_string(data, NULL, &xdata) < 0){
|
||||
if (xml_parse_string(data, NULL, &xdata0) < 0){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
|
|
@ -807,7 +808,7 @@ api_data_put(clicon_handle h,
|
|||
}
|
||||
}
|
||||
else{
|
||||
if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){
|
||||
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
|
|
@ -831,7 +832,7 @@ api_data_put(clicon_handle h,
|
|||
/* The message-body MUST contain exactly one instance of the
|
||||
* expected data resource.
|
||||
*/
|
||||
if (xml_child_nr(xdata) != 1){
|
||||
if (xml_child_nr(xdata0) != 1){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
|
|
@ -842,9 +843,9 @@ api_data_put(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
x = xml_child_i(xdata,0);
|
||||
xdata = xml_child_i(xdata0,0);
|
||||
/* Add operation (create/replace) as attribute */
|
||||
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
||||
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||
|
|
@ -852,24 +853,30 @@ api_data_put(clicon_handle h,
|
|||
#if 0
|
||||
if (debug){
|
||||
cbuf *ccc=cbuf_new();
|
||||
if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0)
|
||||
if (clicon_xml2cbuf(ccc, xdata0, 0, 0) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc));
|
||||
}
|
||||
#endif
|
||||
/* Replace xparent with x, ie bottom of api-path with data */
|
||||
if (api_path==NULL && strcmp(xml_name(x),"data")==0){
|
||||
if (xml_addsub(NULL, x) < 0)
|
||||
/* Top-of tree, no api-path
|
||||
* Replace xparent with x, ie bottom of api-path with data
|
||||
*/
|
||||
dname = xml_name(xdata);
|
||||
if (api_path==NULL && strcmp(dname,"data")==0){
|
||||
if (xml_addsub(NULL, xdata) < 0)
|
||||
goto done;
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
xtop = x;
|
||||
xtop = xdata;
|
||||
xml_name_set(xtop, "config");
|
||||
}
|
||||
else {
|
||||
clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, xml_name(x), xml_name(xbot));
|
||||
/* There is an api-path that defines an element in the datastore tree.
|
||||
* Not top-of-tree.
|
||||
*/
|
||||
clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, dname, xml_name(xbot));
|
||||
/* Check same symbol in api-path as data */
|
||||
if (strcmp(xml_name(x), xml_name(xbot))){
|
||||
if (strcmp(dname, xml_name(xbot))){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
|
|
@ -889,27 +896,12 @@ api_data_put(clicon_handle h,
|
|||
*/
|
||||
xparent = xml_parent(xbot);
|
||||
if (y){
|
||||
yp = yang_parent_get(y);
|
||||
/* Ensure list keys match between uri and data */
|
||||
if (((yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST) &&
|
||||
match_list_keys(y, x, xbot) < 0)){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 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, use_xml) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
|
||||
}
|
||||
/* Ensure list keys match between uri and data of parents
|
||||
* XXX: this may only be a special case of immediate parent
|
||||
/* Ensure list keys match between uri and data. That is:
|
||||
* If data is on the form: -d {"a":{"k":1}} where a is list or leaf-list
|
||||
* then uri-path must be ../a=1
|
||||
* match_list_key() checks if this is true
|
||||
*/
|
||||
if ((yp && yang_keyword_get(yp) == Y_LIST &&
|
||||
xml_parent(x) != xdata &&
|
||||
match_list_keys(yp, xml_parent(x), xparent) < 0)){
|
||||
if (match_list_keys(y, xdata, xbot) < 0){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
|
||||
|
|
@ -919,15 +911,40 @@ api_data_put(clicon_handle h,
|
|||
if (api_return_err(h, r, xe, pretty, use_xml) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
|
||||
}
|
||||
/* Ensure keys in lists are not changed. That is:
|
||||
* If data is on the form: -d {"k":1} and its parent is a list "a"
|
||||
* then the uri-path must be "../a=1 (you cannot change a's key)"
|
||||
*/
|
||||
if ((yp = yang_parent_get(y)) != NULL &&
|
||||
yang_keyword_get(yp) == Y_LIST){
|
||||
if ((ret = yang_key_match(yp, dname)) < 0)
|
||||
goto done;
|
||||
if (ret == 1){ /* Match: xdata is a key */
|
||||
char *parbod = xml_find_body(xparent, dname);
|
||||
/* Check if the key is different from the one in uri-path,
|
||||
* or does not exist
|
||||
*/
|
||||
if (parbod == NULL || strcmp(parbod, xml_body(xdata))){
|
||||
if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 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, use_xml) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xml_purge(xbot);
|
||||
if (xml_addsub(xparent, x) < 0)
|
||||
if (xml_addsub(xparent, xdata) < 0)
|
||||
goto done;
|
||||
/* If we already have that default namespace, remove it in child */
|
||||
if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL){
|
||||
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){
|
||||
if (xml2ns(xparent, NULL, &namespace0) < 0)
|
||||
goto done;
|
||||
/* Set xmlns="" default namespace attribute (if diff from default) */
|
||||
|
|
@ -1015,8 +1032,8 @@ api_data_put(clicon_handle h,
|
|||
xml_free(xretdis);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (xdata)
|
||||
xml_free(xdata);
|
||||
if (xdata0)
|
||||
xml_free(xdata0);
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -3590,6 +3590,7 @@ yang_arg2cvec(yang_stmt *ys,
|
|||
|
||||
/*! Check if yang node yn has key-stmt as child which matches name
|
||||
*
|
||||
* The function looks at the LIST argument string (not actual children)
|
||||
* @param[in] yn Yang node with sub-statements (look for a key child)
|
||||
* @param[in] name Check if this name (eg "b") is a key in the yang key statement
|
||||
*
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ if [ $BE -ne 0 ]; then
|
|||
start_backend -s init -f $cfg
|
||||
|
||||
new "waiting"
|
||||
sleep $RCWAIT
|
||||
wait_backend
|
||||
fi
|
||||
|
||||
new "cli configure top"
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ if [ $BE -ne 0 ]; then
|
|||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill clixon_backend # to be sure
|
||||
new "start backend -s init -f $cfg -- -s"
|
||||
start_backend -s init -f $cfg -- -s
|
||||
fi
|
||||
|
|
@ -57,8 +58,6 @@ new "waiting"
|
|||
wait_backend
|
||||
wait_restconf
|
||||
|
||||
new "restconf tests"
|
||||
|
||||
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
||||
expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
|
||||
<Link rel='restconf' href='/restconf'/>
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ fyang=$dir/restconf.yang
|
|||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>/usr/local/var</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
|
|
@ -66,7 +66,7 @@ module example{
|
|||
}
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg -y $fyang"
|
||||
new "test params: -f $cfg"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
|
|
@ -74,22 +74,21 @@ if [ $BE -ne 0 ]; then
|
|||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg -y $fyang"
|
||||
start_backend -s init -f $cfg -y $fyang
|
||||
sudo pkill clixon_backend # to be sure
|
||||
new "start backend -s init -f $cfg"
|
||||
start_backend -s init -f $cfg
|
||||
fi
|
||||
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||
|
||||
new "start restconf daemon"
|
||||
start_restconf -f $cfg -y $fyang
|
||||
start_restconf -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
wait_restconf
|
||||
|
||||
new "restconf tests"
|
||||
|
||||
new "restconf POST tree without key"
|
||||
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"type":"regular"}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "name"},"error-severity": "error","error-message": "Mandatory key"}}}
'
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ fyang=$dir/list.yang
|
|||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>/usr/local/var</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||
|
|
@ -32,13 +31,17 @@ module list{
|
|||
prefix ex;
|
||||
container c{
|
||||
list a{
|
||||
key b;
|
||||
key "b c";
|
||||
leaf b{
|
||||
type string;
|
||||
}
|
||||
leaf c{
|
||||
type string;
|
||||
}
|
||||
leaf nonkey{
|
||||
description "non-key element";
|
||||
type string;
|
||||
}
|
||||
}
|
||||
leaf-list d{
|
||||
type string;
|
||||
|
|
@ -55,6 +58,8 @@ if [ $BE -ne 0 ]; then
|
|||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill clixon_backend # to be sure
|
||||
|
||||
new "start backend -s init -f $cfg"
|
||||
start_backend -s init -f $cfg
|
||||
fi
|
||||
|
|
@ -69,16 +74,25 @@ new "waiting"
|
|||
wait_backend
|
||||
wait_restconf
|
||||
|
||||
new "restconf PUT add list entry"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x","c":"0"}}' 0 ''
|
||||
new "restconf PUT add whole list entry"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"x","c":"y","nonkey":"0"}}' 0 ''
|
||||
|
||||
new "restconf PUT change regular list entry"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x","c":"z"}}' 0 ''
|
||||
new "restconf PUT change whole list entry (same keys)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"x","c":"y","nonkey":"z"}}' 0 ''
|
||||
|
||||
new "restconf PUT change list key entry (expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"y"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}'
|
||||
new "restconf PUT change list entry (wrong keys)(expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y -d {"list:a":{"b":"y","c":"x"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}'
|
||||
|
||||
new "restconf PUT change actual list key entry (expect fail)"
|
||||
new "restconf PUT change list entry (just one key)(expect fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x -d {"list:a":{"b":"x"}}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}'
|
||||
|
||||
new "restconf PUT sub non-key"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x,y/nonkey -d {"list:nonkey":"u"}' 0 ''
|
||||
|
||||
new "restconf PUT sub key same value"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/b -d {"b":"x"}' 0 ''
|
||||
|
||||
new "restconf PUT just key other value (should fail)"
|
||||
expectfn 'curl -s -X PUT http://localhost/restconf/data/list:c/a=x/b -d {"b":"y"}' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}'
|
||||
|
||||
new "restconf PUT add leaf-list entry"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue