* RESTCONF "insert" and "point" query parameters supported

* Yang Netconf leaf/leaf-list insert support
  * For "ordered-by user" leafs and leaf-lists, the insert and value/key attributes are supported according to RFC7950 Sections 7.7.9 and 7.8.6

* Fixed RESTCONF api-path leaf-list selection was not made properly
This commit is contained in:
Olof hagsand 2019-07-31 14:04:27 +02:00
parent d7a8cf5b0c
commit 2d9d204f69
20 changed files with 740 additions and 105 deletions

View file

@ -122,6 +122,55 @@ replace_xmlns(cxobj *x0,
return retval;
}
/*! Given an attribute name and its expected namespace, find its value
*
* An attribute may have a prefix(or NULL). The routine finds the associated
* xmlns binding to find the namespace: <namespace>:<name>.
* If such an attribute is not found, failure is returned with cbret set,
* If such an attribute its found, its string value is returned.
* @param[in] x XML node (where to look for attribute)
* @param[in] name Attribute name
* @param[in] namespace (Expected)Namespace of attribute
* @param[out] cbret Error message (if retval=0)
* @param[out] valp Pointer to value (if retval=1)
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
*/
static int
attr_ns_value(cxobj *x,
char *name,
char *namespace,
cbuf *cbret,
char **valp)
{
int retval = -1;
cxobj *xa;
char *ans = NULL; /* attribute namespace */
char *val = NULL;
/* prefix=NULL since we do not know the prefix */
if ((xa = xml_find_type(x, NULL, name, CX_ATTR)) != NULL){
if (xml2ns(xa, xml_prefix(xa), &ans) < 0)
goto done;
if (ans == NULL){ /* the attribute exists, but no namespace */
if (netconf_bad_attribute(cbret, "application", name, "Unresolved attribute prefix (no namespace?)") < 0)
goto done;
goto fail;
}
/* the attribute exists, but not w expected namespace */
if (strcmp(ans, namespace) == 0)
val = xml_value(xa);
}
*valp = val;
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th Datastore text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
@ -138,6 +187,10 @@ replace_xmlns(cxobj *x0,
* @retval 1 OK
* Assume x0 and x1 are same on entry and that y is the spec
* @see text_modify_top
* RFC 7950 Sec 7.7.9(leaf-list), 7.8.6(lists)
* In an "ordered-by user" list, the attributes "insert" and "key" in
* the YANG XML namespace can be used to control where
* in the list the entry is inserted.
*/
static int
text_modify(clicon_handle h,
@ -152,7 +205,7 @@ text_modify(clicon_handle h,
cbuf *cbret)
{
int retval = -1;
char *opstr;
char *opstr = NULL;
char *x1name;
char *x1cname; /* child name */
cxobj *x0a; /* attribute */
@ -167,22 +220,66 @@ text_modify(clicon_handle h,
cxobj **x0vec = NULL;
int i;
int ret;
char *instr = NULL;
char *keystr = NULL;
char *valstr = NULL;
enum insert_type insert = INS_LAST;
int changed = 0; /* Only if x0p's children have changed-> sort is necessary */
/* Check for operations embedded in tree according to netconf */
#ifdef notyet /* XXX breaks in test_cohoice.sh */
if ((ret = attr_ns_value(x1,
"operation", "urn:ietf:params:xml:ns:netconf:base:1.0",
cbret, &opstr)) < 0)
goto done;
if (ret == 0)
goto fail;
if (opstr != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
#else
if ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
#endif
x1name = xml_name(x1);
if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){
/* This is a check on no further elements as a sanity check for eg
* <leaf>a<leaf>b</leaf></leaf>
*/
if (yang_keyword_get(y0) == Y_LEAF_LIST ||
yang_keyword_get(y0) == Y_LEAF){
/* This is a check that a leaf does not have sub-elements
* such as: <leaf>a <leaf>b</leaf> </leaf>
*/
if (xml_child_nr_type(x1, CX_ELMNT)){
if (netconf_unknown_element(cbret, "application", x1name, "Leaf contains sub-element") < 0)
goto done;
goto fail;
}
/* If leaf-list and ordered-by user, then get yang:insert attribute
* See RFC 7950 Sec 7.7.9
*/
if (yang_keyword_get(y0) == Y_LEAF_LIST &&
yang_find(y0, Y_ORDERED_BY, "user") != NULL){
if ((ret = attr_ns_value(x1,
"insert", "urn:ietf:params:xml:ns:yang:1",
cbret, &instr)) < 0)
goto done;
if (ret == 0)
goto fail;
if (instr != NULL &&
xml_attr_insert2val(instr, &insert) < 0)
goto done;
if ((ret = attr_ns_value(x1,
"value", "urn:ietf:params:xml:ns:yang:1",
cbret, &valstr)) < 0)
goto done;
/* if insert/before, value attribute must be there */
if ((insert == INS_AFTER || insert == INS_BEFORE) &&
valstr == NULL){
if (netconf_missing_attribute(cbret, "application", "<bad-attribute>value</bad-attribute>", "Missing value attribute when insert is before or after") < 0)
goto done;
goto fail;
}
}
x1bstr = xml_body(x1);
switch(op){
case OP_CREATE:
@ -191,9 +288,29 @@ text_modify(clicon_handle h,
goto done;
goto fail;
}
case OP_NONE: /* fall thru */
case OP_REPLACE: /* fall thru */
case OP_MERGE:
case OP_REPLACE:
if (!(op == OP_MERGE && instr==NULL)){
/* Remove existing, also applies to merge in the special case
* of ordered-by user and (changed) insert attribute.
*/
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
/* XXX: Note, if there is an error in adding the object later, the
* original object is not reverted.
*/
if (x0){
xml_purge(x0);
x0 = NULL;
}
} /* OP_MERGE & insert */
case OP_NONE: /* fall thru */
if (x0==NULL){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
@ -208,11 +325,15 @@ text_modify(clicon_handle h,
goto done;
changed++;
/* Copy xmlns attributes */
/* Copy xmlns attributes ONLY, not op/insert etc */
x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
if (strcmp(xml_value(x1a),"urn:ietf:params:xml:ns:yang:1")==0)
continue;
#endif
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
@ -261,8 +382,8 @@ text_modify(clicon_handle h,
}
}
}
if (changed){
if (xml_insert(x0p, x0) < 0)
if (changed){
if (xml_insert(x0p, x0, insert, valstr) < 0)
goto done;
}
break;
@ -289,6 +410,37 @@ text_modify(clicon_handle h,
} /* switch op */
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */
/* If list and ordered-by user, then get insert attribute
<user nc:operation="create"
yang:insert="after"
yang:key="[ex:first-name='fred']
[ex:surname='flintstone']">
* See RFC 7950 Sec 7.8.6
*/
if (yang_keyword_get(y0) == Y_LIST &&
yang_find(y0, Y_ORDERED_BY, "user") != NULL){
if ((ret = attr_ns_value(x1,
"insert", "urn:ietf:params:xml:ns:yang:1",
cbret, &instr)) < 0)
goto done;
if (ret == 0)
goto fail;
if (instr != NULL &&
xml_attr_insert2val(instr, &insert) < 0)
goto done;
if ((ret = attr_ns_value(x1,
"key", "urn:ietf:params:xml:ns:yang:1",
cbret, &keystr)) < 0)
goto done;
/* if insert/before, key attribute must be there */
if ((insert == INS_AFTER || insert == INS_BEFORE) &&
keystr == NULL){
if (netconf_missing_attribute(cbret, "application", "<bad-attribute>key</bad-attribute>", "Missing key attribute when insert is before or after") < 0)
goto done;
goto fail;
}
}
switch(op){
case OP_CREATE:
if (x0){
@ -297,19 +449,27 @@ text_modify(clicon_handle h,
goto fail;
}
case OP_REPLACE: /* fall thru */
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if (x0){
xml_purge(x0);
x0 = NULL;
}
case OP_MERGE: /* fall thru */
case OP_NONE:
case OP_MERGE:
if (!(op == OP_MERGE && instr==NULL)){
/* Remove existing, also applies to merge in the special case
* of ordered-by user and (changed) insert attribute.
*/
if (!permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
/* XXX: Note, if there is an error in adding the object later, the
* original object is not reverted.
*/
if (x0){
xml_purge(x0);
x0 = NULL;
}
} /* OP_MERGE & insert */
case OP_NONE: /* fall thru */
/* Special case: anyxml, just replace tree,
See rfc6020 7.10.3:n
An anyxml node is treated as an opaque chunk of data. This data
@ -356,6 +516,10 @@ text_modify(clicon_handle h,
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){
#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */
if (strcmp(xml_value(x1a),"urn:ietf:params:xml:ns:yang:1")==0)
continue;
#endif
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
@ -412,7 +576,7 @@ text_modify(clicon_handle h,
goto fail;
}
if (changed){
if (xml_insert(x0p, x0) < 0)
if (xml_insert(x0p, x0, insert, keystr) < 0)
goto done;
}
break;

View file

@ -625,6 +625,7 @@ xml_child_nr_type(cxobj *xn,
* @retval xml The child xml node
* @retval NULL if no such child, or empty child
* @see xml_child_i_type
* @see xml_child_order
*/
cxobj *
xml_child_i(cxobj *xn,
@ -673,6 +674,29 @@ xml_child_i_set(cxobj *xt,
return 0;
}
/*! Get the order of child
* @param[in] xp xml parent node
* @param[in] xc the xml child to look for
* @retval xml The child xml node
* @retval i The order of the child
* @retval -1 if no such child, or empty child
* @see xml_child_i
*/
int
xml_child_order(cxobj *xp,
cxobj *xc)
{
cxobj *x = NULL;
int i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
return i;
i++;
}
return -1;
}
/*! Iterator over xml children objects
*
* @note Never manipulate the child-list during operation or using the
@ -2375,6 +2399,8 @@ xml_operation(char *opstr,
return 0;
}
/*! Map xml operation from enumeration to string
* @param[in] op enumeration operation, eg OP_MERGE,...
* @retval str String, eg "merge". Static string, no free necessary
@ -2406,6 +2432,32 @@ xml_operation2str(enum operation_type op)
return "none";
}
}
/*! Map xml insert attribute from string to enumeration
* @param[in] instr String, eg "first"
* @param[out] ins Enumeration, eg INS_FIRST
* @code
* enum insert_type ins;
* xml_operation("last", &ins)
* @endcode
*/
int
xml_attr_insert2val(char *instr,
enum insert_type *ins)
{
if (strcmp("first", instr) == 0)
*ins = INS_FIRST;
else if (strcmp("last", instr) == 0)
*ins = INS_LAST;
else if (strcmp("before", instr) == 0)
*ins = INS_BEFORE;
else if (strcmp("after", instr) == 0)
*ins = INS_AFTER;
else{
clicon_err(OE_XML, 0, "Bad-attribute operation: %s", instr);
return -1;
}
return 0;
}
/*! Specialization of clicon_debug with xml tree
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG.

View file

@ -2198,7 +2198,7 @@ xml_default(cxobj *xt,
goto done;
free(str);
added++;
if (xml_insert(xt, xc) < 0)
if (xml_insert(xt, xc, INS_LAST, NULL) < 0)
goto done;
}
}
@ -2455,21 +2455,33 @@ api_path2xpath_cvv(cvec *api_path,
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
v = val;
/* XXX sync with yang */
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v);
v += strlen(v)+1;
switch (yang_keyword_get(y)){
case Y_LIST:
v = val;
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v);
v += strlen(v)+1;
}
break;
case Y_LEAF_LIST: /* XXX: LOOP? */
cprintf(xpath, "/%s", name);
if (val)
cprintf(xpath, "[.='%s']", val);
else
cprintf(xpath, "[.='']");
break;
default:
cprintf(xpath, "%s%s", (i==offset?"":"/"), name);
break;
}
if (val)
free(val);
@ -2516,7 +2528,7 @@ api_path2xpath_cvv(cvec *api_path,
* ... access xpath as cbuf_get(xpath)
* free(xpath)
* @endcode
*
* @see api_path2xml_cvv which uses other parameter formats
*/
int

View file

@ -61,6 +61,8 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_options.h"
#include "clixon_xml_map.h"
#include "clixon_yang_type.h"
@ -361,30 +363,35 @@ xml_sort(cxobj *x,
}
/*! Special case search for ordered-by user where linear sort is used
* @param[in] xp Parent XML node (go through its childre)
* @param[in] x1 XML node to match
* @param[in] yangi Yang order number (according to spec)
* @param[in] mid Where to start from (may be in middle of interval)
* @retval NULL Not found
* @retval x XML element that matches x1
*/
static cxobj *
xml_search_userorder(cxobj *xp,
cxobj *x1,
yang_stmt *y,
int yangi,
int mid)
{
int i;
cxobj *xc;
int i;
cxobj *xc;
yang_stmt *yc;
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
xc = xml_child_i(xp, i);
y = xml_spec(xc);
if (yangi!=yang_order(y))
yc = xml_spec(xc);
if (yangi!=yang_order(yc))
break;
if (xml_cmp(xc, x1, 0) == 0)
return xc;
}
for (i=mid-1; i>=0; i--){ /* Then decrement */
xc = xml_child_i(xp, i);
y = xml_spec(xc);
if (yangi!=yang_order(y))
yc = xml_spec(xc);
if (yangi!=yang_order(yc))
break;
if (xml_cmp(xc, x1, 0) == 0)
return xc;
@ -427,7 +434,7 @@ xml_search1(cxobj *xp,
if (cmp == 0){
cmp = xml_cmp(x1, xc, 0);
if (cmp && userorder) /* Ordered by user (if not equal) */
return xml_search_userorder(xp, x1, y, yangi, mid);
return xml_search_userorder(xp, x1, yangi, mid);
}
if (cmp == 0)
return xc;
@ -472,25 +479,123 @@ xml_search(cxobj *xp,
return xret;
}
/*! Insert xn in xp:s sorted child list (special case of ordered-by user)
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] xn Child xml node to insert under xp
* @param[in] yn Yang stmt of xml child node
* @param[in] ins Insert operation (if ordered-by user)
* @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
* @retval i Order where xn should be inserted into xp:s children
* @retval -1 Error
*/
static int
xml_insert_userorder(cxobj *xp,
cxobj *xn,
yang_stmt *yn,
int mid,
enum insert_type ins,
char *key_val)
{
int retval = -1;
int i;
cxobj *xc;
yang_stmt *yc;
char *kludge = ""; /* Cant get instance-id generic of [.. and /.. case */
switch (ins){
case INS_FIRST:
for (i=mid-1; i>=0; i--){ /* decrement */
xc = xml_child_i(xp, i);
yc = xml_spec(xc);
if (yc != yn){
retval = i+1;
goto done;
}
}
retval = i+1;
break;
case INS_LAST:
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
xc = xml_child_i(xp, i);
yc = xml_spec(xc);
if (yc != yn){
retval = i;
goto done;
}
}
retval = i;
break;
case INS_BEFORE:
case INS_AFTER: /* see retval handling different between before and after */
if (key_val == NULL)
/* shouldnt happen */
clicon_err(OE_YANG, 0, "Missing key/value attribute when insert is before");
else{
switch (yang_keyword_get(yn)){
case Y_LEAF_LIST:
if ((xc = xpath_first(xp, "%s", key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: value, missing-instance: %s", key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");
else
retval = (ins==INS_BEFORE)?i:i+1;
}
break;
case Y_LIST:
if (strlen(key_val) && key_val[0] == '[')
kludge = xml_name(xn);
if ((xc = xpath_first(xp, "%s%s", kludge, key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s%s", xml_name(xn), key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");
else
retval = (ins==INS_BEFORE)?i:i+1;
}
break;
default:
clicon_err(OE_YANG, 0, "insert only for leaf or leaf-list");
break;
} /* switch */
}
}
done:
return retval;
}
/*! Insert xn in xp:s sorted child list
* Find a point in xp childvec with two adjacent nodes xi,xi+1 such that
* xi<=xn<=xi+1 or xn<=x0 or xmax<=xn
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] xn Child xml node to insert under xp
* @param[in] yn Yang stmt of xml child node
* @param[in] yni yang order
* @param[in] userorder Set if ordered-by user, otherwise 0
* @param[in] ins Insert operation (if ordered-by user)
* @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
* @param[in] low Lower range limit
* @param[in] upper Upper range limit
* @retval i Order where xn should be inserted into xp:s children
* @retval -1 Error
*/
static int
xml_insert2(cxobj *xp,
cxobj *xn,
yang_stmt *yn,
int yni,
int userorder,
int low,
int upper)
xml_insert2(cxobj *xp,
cxobj *xn,
yang_stmt *yn,
int yni,
int userorder,
enum insert_type ins,
char *key_val,
int low,
int upper)
{
int retval = -1;
int mid;
int cmp;
cxobj *xc;
yang_stmt *yc;
int i;
if (low > upper){ /* beyond range */
clicon_err(OE_XML, 0, "low>upper %d %d", low, upper);
@ -512,15 +617,7 @@ xml_insert2(cxobj *xp,
}
if (yc == yn){ /* Same yang */
if (userorder){ /* append: increment linearly until no longer equal */
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
xc = xml_child_i(xp, i);
yc = xml_spec(xc);
if (yc != yn){
retval = i;
goto done;
}
}
retval = i;
retval = xml_insert_userorder(xp, xn, yn, mid, ins, key_val);
goto done;
}
else /* Ordered by system */
@ -546,23 +643,27 @@ xml_insert2(cxobj *xp,
goto done;
}
else if (cmp < 0)
return xml_insert2(xp, xn, yn, yni, userorder, low, mid);
return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, low, mid);
else
return xml_insert2(xp, xn, yn, yni, userorder, mid+1, upper);
return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, mid+1, upper);
done:
return retval;
}
/*! Insert xc as child to xp in sorted place. Remove xc from previous parent.
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] x Child xml node to insert under xp
* @retval 0 OK
* @retval -1 Error
* @param[in] xp Parent xml node. If NULL just remove from old parent.
* @param[in] x Child xml node to insert under xp
* @param[in] ins Insert operation (if ordered-by user)
* @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
* @retval 0 OK
* @retval -1 Error
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
*/
int
xml_insert(cxobj *xp,
cxobj *xi)
xml_insert(cxobj *xp,
cxobj *xi,
enum insert_type ins,
char *key_val)
{
int retval = -1;
cxobj *xa;
@ -596,7 +697,9 @@ xml_insert(cxobj *xp,
else if (yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST)
userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL);
yi = yang_order(y);
if ((i = xml_insert2(xp, xi, y, yi, userorder, low, upper)) < 0)
if ((i = xml_insert2(xp, xi, y, yi,
userorder, ins, key_val,
low, upper)) < 0)
goto done;
if (xml_child_insert_pos(xp, xi, i) < 0)
goto done;