* 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

@ -4,6 +4,7 @@
### Major New features ### Major New features
* Restconf RFC 8040 increased feature compliance * Restconf RFC 8040 increased feature compliance
* RESTCONF "insert" and "point" query parameters supported
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources * `201 Created` for created resources
* `204 No Content` for replaced resources. * `204 No Content` for replaced resources.
@ -14,6 +15,8 @@
* HTTP `Location:` fields added in RESTCONF POST replies * HTTP `Location:` fields added in RESTCONF POST replies
* HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5) * HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5)
* Restconf monitoring capabilities (RFC Section 9.1) * Restconf monitoring capabilities (RFC Section 9.1)
* 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
* Yang extensions support * Yang extensions support
* New plugin callback: ca_extension * New plugin callback: ca_extension
* The main example explains how to implement a Yang extension in a backend plugin. * The main example explains how to implement a Yang extension in a backend plugin.
@ -38,6 +41,8 @@
* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions * pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
### Corrected Bugs ### Corrected Bugs
* Fixed RESTCONF api-path leaf-list selection was not made properly
* Requesting eg `mod:x/y=42` returned the whole list: `{"y":[41,42,43]}` whereas it should only return one element: `{"y":42}`
* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56) * See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error. * Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.

View file

@ -235,15 +235,16 @@ Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available t
run with NGINX. run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040). The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
The following features are supported: The following features of RFC8040 are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE - OPTIONS, HEAD, GET, POST, PUT, DELETE
- stream notifications (RFC8040 sec 6) - stream notifications (Sec 6)
- query parameters start-time and stop-time(RFC8040 section 4.9) - query parameters: "insert", "point", "start-time" and "stop-time".
- Monitoring (Sec 9)
The following features are not implemented: The following features are not implemented:
- ETag/Last-Modified - ETag/Last-Modified
- PATCH - PATCH
- query parameters other than start/stop-time. - Query parameters: "content", "depth", "fields", "filter", "with-defaults"
See [more detailed instructions](apps/restconf/README.md). See [more detailed instructions](apps/restconf/README.md).

View file

@ -305,6 +305,7 @@ printparam(FCGX_Request *r,
/*! Print all FCGI headers /*! Print all FCGI headers
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/ */
int int
restconf_test(FCGX_Request *r, restconf_test(FCGX_Request *r,
@ -552,3 +553,96 @@ restconf_terminate(clicon_handle h)
return 0; return 0;
} }
/*! If restconf insert/point attributes are present, translate to netconf
* @param[in] xdata URI->XML to translate
* @param[in] qvec Query parameters (eg where insert/point should be)
* @retval 0 OK
* @retval -1 Error
*/
int
restconf_insert_attributes(cxobj *xdata,
cvec *qvec)
{
int retval = -1;
cxobj *xa;
char *instr;
char *pstr;
yang_stmt *y;
char *attrname;
int ret;
y = xml_spec(xdata);
if ((instr = cvec_find_str(qvec, "insert")) != NULL){
/* First add xmlns:yang attribute */
if ((xa = xml_new("yang", xdata, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, "urn:ietf:params:xml:ns:yang:1") < 0)
goto done;
/* Then add insert attribute */
if ((xa = xml_new("insert", xdata, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "yang") < 0)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, instr) < 0)
goto done;
}
if ((pstr = cvec_find_str(qvec, "point")) != NULL){
char *xpath = NULL;
char *namespace = NULL;
cbuf *cb = NULL;
if (y == NULL){
clicon_err(OE_YANG, 0, "Cannot yang resolve %s", xml_name(xdata));
goto done;
}
if (yang_keyword_get(y) == Y_LIST)
attrname="key";
else
attrname="value";
/* Then add value/key attribute */
if ((xa = xml_new(attrname, xdata, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "yang") < 0)
goto done;
xml_type_set(xa, CX_ATTR);
if ((ret = api_path2xpath(pstr, ys_spec(y), &xpath, &namespace)) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "/%s", xpath); /* XXX: also prefix/namespace? */
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
if (xpath)
free(xpath);
if (cb)
cbuf_free(cb);
}
retval = 0;
done:
return retval;
}
/*! Extract uri-encoded uri-path from fastcgi parameters
* Use REQUEST_URI parameter and strip ?args
* REQUEST_URI have args and is encoded
* eg /interface=eth%2f0%2f0?insert=first
* DOCUMENT_URI dont have args and is not encoded
* eg /interface=eth/0/0
* causes problems with eg /interface=eth%2f0%2f0
*/
char *
restconf_uripath(FCGX_Request *r)
{
char *path;
char *q;
path = FCGX_GetParam("REQUEST_URI", r->envp);
if ((q = index(path, '?')) != NULL)
*q = '\0';
return path;
}

View file

@ -63,5 +63,7 @@ int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
int pretty, int use_xml, int code); int pretty, int use_xml, int code);
int http_location(FCGX_Request *r, cxobj *xobj); int http_location(FCGX_Request *r, cxobj *xobj);
int restconf_terminate(clicon_handle h); int restconf_terminate(clicon_handle h);
int restconf_insert_attributes(cxobj *xdata, cvec *qvec);
char *restconf_uripath(FCGX_Request *r);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -345,7 +345,7 @@ api_restconf(clicon_handle h,
cxobj *xerr; cxobj *xerr;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("REQUEST_URI", r->envp); path = restconf_uripath(r);
query = FCGX_GetParam("QUERY_STRING", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* get xml/json in put and output */ /* get xml/json in put and output */

View file

@ -418,7 +418,6 @@ api_data_put(clicon_handle h,
op = OP_CREATE; op = OP_CREATE;
if (xml_value_set(xa, xml_operation2str(op)) < 0) if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done; goto done;
/* Top-of tree, no api-path /* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data * Replace xparent with x, ie bottom of api-path with data
*/ */
@ -522,6 +521,9 @@ api_data_put(clicon_handle h,
goto ok; goto ok;
} }
} }
/* If restconf insert/point attributes are present, translate to netconf */
if (restconf_insert_attributes(xdata, qvec) < 0)
goto done;
/* If we already have that default namespace, remove it in child */ /* If we already have that default namespace, remove it in child */
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){ if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){

View file

@ -132,9 +132,8 @@ api_data_post(clicon_handle h,
int nullspec = 0; int nullspec = 0;
int ret; int ret;
clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"", clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path);
__FUNCTION__, clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
api_path, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
@ -164,6 +163,15 @@ api_data_post(clicon_handle h,
goto ok; goto ok;
} }
} }
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0)
goto done;
clicon_debug(1, "%s XURI:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* Parse input data as json or xml into xml */ /* Parse input data as json or xml into xml */
if (parse_xml){ if (parse_xml){
if (xml_parse_string(data, NULL, &xdata0) < 0){ if (xml_parse_string(data, NULL, &xdata0) < 0){
@ -224,6 +232,7 @@ api_data_post(clicon_handle h,
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 /* If the api-path (above) defines a module, then xdata must have a prefix
* and it match the module defined in api-path. * and it match the module defined in api-path.
* In a POST, maybe there are cornercases where xdata (which is a child) and * In a POST, maybe there are cornercases where xdata (which is a child) and
@ -274,6 +283,19 @@ api_data_post(clicon_handle h,
} }
} }
/* If restconf insert/point attributes are present, translate to netconf */
if (restconf_insert_attributes(xdata, qvec) < 0)
goto done;
#if 1
if (debug){
cbuf *ccc=cbuf_new();
if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0)
goto done;
clicon_debug(1, "%s XDATA:%s", __FUNCTION__, cbuf_get(ccc));
cbuf_free(ccc);
}
#endif
/* Create text buffer for transfer to backend */ /* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL){ if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new"); clicon_err(OE_UNIX, 0, "cbuf_new");

View file

@ -366,7 +366,7 @@ api_stream(clicon_handle h,
#endif #endif
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp); path = restconf_uripath(r);
query = FCGX_GetParam("QUERY_STRING", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
restconf_test(r, 1); restconf_test(r, 1);

View file

@ -51,7 +51,7 @@
* Types * Types
*/ */
/* Netconf operation type */ /* Netconf operation type */
enum operation_type{ /* edit-configo */ enum operation_type{ /* edit-config operation */
OP_MERGE, /* merge config-data */ OP_MERGE, /* merge config-data */
OP_REPLACE,/* replace or create config-data */ OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */ OP_CREATE, /* create config data, error if exist */
@ -60,6 +60,14 @@ enum operation_type{ /* edit-configo */
OP_NONE OP_NONE
}; };
/* Netconf insert type (see RFC7950 Sec 7.8.6) */
enum insert_type{ /* edit-config insert */
INS_FIRST,
INS_LAST,
INS_BEFORE,
INS_AFTER,
};
enum cxobj_type {CX_ERROR=-1, enum cxobj_type {CX_ERROR=-1,
CX_ELMNT, CX_ELMNT,
CX_ATTR, CX_ATTR,
@ -115,6 +123,7 @@ int xml_child_nr_notype(cxobj *xn, enum cxobj_type type);
cxobj *xml_child_i(cxobj *xn, int i); cxobj *xml_child_i(cxobj *xn, int i);
cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type); cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type);
cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
int xml_child_order(cxobj *xn, cxobj *xc);
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type);
int xml_child_insert_pos(cxobj *x, cxobj *xc, int i); int xml_child_insert_pos(cxobj *x, cxobj *xc, int i);
@ -178,6 +187,7 @@ int xml_body_int32(cxobj *xb, int32_t *val);
int xml_body_uint32(cxobj *xb, uint32_t *val); int xml_body_uint32(cxobj *xb, uint32_t *val);
int xml_operation(char *opstr, enum operation_type *op); int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op); char *xml_operation2str(enum operation_type op);
int xml_attr_insert2val(char *instr, enum insert_type *ins);
#if defined(__GNUC__) && __GNUC__ >= 3 #if defined(__GNUC__) && __GNUC__ >= 3
int clicon_log_xml(int level, cxobj *x, char *format, ...) __attribute__ ((format (printf, 3, 4))); int clicon_log_xml(int level, cxobj *x, char *format, ...) __attribute__ ((format (printf, 3, 4)));
#else #else

View file

@ -56,7 +56,6 @@ int xml_yang_validate_all(clicon_handle h, cxobj *xt, cxobj **xret);
int xml_yang_validate_all_top(clicon_handle h, cxobj *xt, cxobj **xret); int xml_yang_validate_all_top(clicon_handle h, cxobj *xt, cxobj **xret);
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0);
int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1, int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1,
cxobj ***first, size_t *firstlen, cxobj ***first, size_t *firstlen,
cxobj ***second, size_t *secondlen, cxobj ***second, size_t *secondlen,

View file

@ -42,7 +42,7 @@
int xml_child_spec(cxobj *x, cxobj *xp, yang_stmt *yspec, yang_stmt **yp); int xml_child_spec(cxobj *x, cxobj *xp, yang_stmt *yspec, yang_stmt **yp);
int xml_cmp(cxobj *x1, cxobj *x2, int enm); int xml_cmp(cxobj *x1, cxobj *x2, int enm);
int xml_sort(cxobj *x0, void *arg); int xml_sort(cxobj *x0, void *arg);
int xml_insert(cxobj *xp, cxobj *xc); int xml_insert(cxobj *xp, cxobj *xc, enum insert_type ins, char *key_val);
int xml_sort_verify(cxobj *x, void *arg); int xml_sort_verify(cxobj *x, void *arg);
int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp); int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp);

View file

@ -62,7 +62,7 @@ enum rfc_6020{
Y_BELONGS_TO, Y_BELONGS_TO,
Y_BIT, Y_BIT,
Y_CASE, Y_CASE,
Y_CHOICE, Y_CHOICE, /* 10 */
Y_CONFIG, Y_CONFIG,
Y_CONTACT, Y_CONTACT,
Y_CONTAINER, Y_CONTAINER,
@ -72,7 +72,7 @@ enum rfc_6020{
Y_DEVIATION, Y_DEVIATION,
Y_ENUM, Y_ENUM,
Y_ERROR_APP_TAG, Y_ERROR_APP_TAG,
Y_ERROR_MESSAGE, Y_ERROR_MESSAGE, /* 20 */
Y_EXTENSION, Y_EXTENSION,
Y_FEATURE, Y_FEATURE,
Y_FRACTION_DIGITS, Y_FRACTION_DIGITS,
@ -82,7 +82,7 @@ enum rfc_6020{
Y_IMPORT, Y_IMPORT,
Y_INCLUDE, Y_INCLUDE,
Y_INPUT, Y_INPUT,
Y_KEY, Y_KEY, /* 30 */
Y_LEAF, Y_LEAF,
Y_LEAF_LIST, Y_LEAF_LIST,
Y_LENGTH, Y_LENGTH,
@ -92,7 +92,7 @@ enum rfc_6020{
Y_MIN_ELEMENTS, Y_MIN_ELEMENTS,
Y_MODIFIER, Y_MODIFIER,
Y_MODULE, Y_MODULE,
Y_MUST, Y_MUST, /* 40 */
Y_NAMESPACE, Y_NAMESPACE,
Y_NOTIFICATION, Y_NOTIFICATION,
Y_ORDERED_BY, Y_ORDERED_BY,
@ -102,7 +102,7 @@ enum rfc_6020{
Y_PATTERN, Y_PATTERN,
Y_POSITION, Y_POSITION,
Y_PREFIX, Y_PREFIX,
Y_PRESENCE, Y_PRESENCE, /* 50 */
Y_RANGE, Y_RANGE,
Y_REFERENCE, Y_REFERENCE,
Y_REFINE, Y_REFINE,
@ -112,7 +112,7 @@ enum rfc_6020{
Y_RPC, Y_RPC,
Y_STATUS, Y_STATUS,
Y_SUBMODULE, Y_SUBMODULE,
Y_TYPE, Y_TYPE, /* 60 */
Y_TYPEDEF, Y_TYPEDEF,
Y_UNIQUE, Y_UNIQUE,
Y_UNITS, Y_UNITS,

View file

@ -122,6 +122,55 @@ replace_xmlns(cxobj *x0,
return retval; 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 /*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th Datastore text handle * @param[in] th Datastore text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x0 Base xml tree (can be NULL in add scenarios)
@ -138,6 +187,10 @@ replace_xmlns(cxobj *x0,
* @retval 1 OK * @retval 1 OK
* Assume x0 and x1 are same on entry and that y is the spec * Assume x0 and x1 are same on entry and that y is the spec
* @see text_modify_top * @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 static int
text_modify(clicon_handle h, text_modify(clicon_handle h,
@ -152,7 +205,7 @@ text_modify(clicon_handle h,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
char *opstr; char *opstr = NULL;
char *x1name; char *x1name;
char *x1cname; /* child name */ char *x1cname; /* child name */
cxobj *x0a; /* attribute */ cxobj *x0a; /* attribute */
@ -167,22 +220,66 @@ text_modify(clicon_handle h,
cxobj **x0vec = NULL; cxobj **x0vec = NULL;
int i; int i;
int ret; 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 */ int changed = 0; /* Only if x0p's children have changed-> sort is necessary */
/* Check for operations embedded in tree according to netconf */ /* 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 ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0) if (xml_operation(opstr, &op) < 0)
goto done; goto done;
#endif
x1name = xml_name(x1); x1name = xml_name(x1);
if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){ if (yang_keyword_get(y0) == Y_LEAF_LIST ||
/* This is a check on no further elements as a sanity check for eg yang_keyword_get(y0) == Y_LEAF){
* <leaf>a<leaf>b</leaf></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 (xml_child_nr_type(x1, CX_ELMNT)){
if (netconf_unknown_element(cbret, "application", x1name, "Leaf contains sub-element") < 0) if (netconf_unknown_element(cbret, "application", x1name, "Leaf contains sub-element") < 0)
goto done; goto done;
goto fail; 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); x1bstr = xml_body(x1);
switch(op){ switch(op){
case OP_CREATE: case OP_CREATE:
@ -191,9 +288,29 @@ text_modify(clicon_handle h,
goto done; goto done;
goto fail; goto fail;
} }
case OP_NONE: /* fall thru */ case OP_REPLACE: /* fall thru */
case OP_MERGE: 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 (x0==NULL){
if ((op != OP_NONE) && !permit && xnacm){ if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
@ -208,11 +325,15 @@ text_modify(clicon_handle h,
goto done; goto done;
changed++; changed++;
/* Copy xmlns attributes */ /* Copy xmlns attributes ONLY, not op/insert etc */
x1a = NULL; x1a = NULL;
while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL) while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 || if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "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) if ((x0a = xml_dup(x1a)) == NULL)
goto done; goto done;
if (xml_addsub(x0, x0a) < 0) if (xml_addsub(x0, x0a) < 0)
@ -262,7 +383,7 @@ text_modify(clicon_handle h,
} }
} }
if (changed){ if (changed){
if (xml_insert(x0p, x0) < 0) if (xml_insert(x0p, x0, insert, valstr) < 0)
goto done; goto done;
} }
break; break;
@ -289,6 +410,37 @@ text_modify(clicon_handle h,
} /* switch op */ } /* switch op */
} /* if LEAF|LEAF_LIST */ } /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */ 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){ switch(op){
case OP_CREATE: case OP_CREATE:
if (x0){ if (x0){
@ -297,19 +449,27 @@ text_modify(clicon_handle h,
goto fail; goto fail;
} }
case OP_REPLACE: /* fall thru */ case OP_REPLACE: /* fall thru */
if (!permit && xnacm){ case OP_MERGE:
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) if (!(op == OP_MERGE && instr==NULL)){
goto done; /* Remove existing, also applies to merge in the special case
if (ret == 0) * of ordered-by user and (changed) insert attribute.
goto fail; */
permit = 1; if (!permit && xnacm){
} if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
if (x0){ goto done;
xml_purge(x0); if (ret == 0)
x0 = NULL; goto fail;
} permit = 1;
case OP_MERGE: /* fall thru */ }
case OP_NONE: /* 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, /* Special case: anyxml, just replace tree,
See rfc6020 7.10.3:n See rfc6020 7.10.3:n
An anyxml node is treated as an opaque chunk of data. This data 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) while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL)
if (strcmp(xml_name(x1a),"xmlns")==0 || if (strcmp(xml_name(x1a),"xmlns")==0 ||
((xns = xml_prefix(x1a)) && strcmp(xns, "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) if ((x0a = xml_dup(x1a)) == NULL)
goto done; goto done;
if (xml_addsub(x0, x0a) < 0) if (xml_addsub(x0, x0a) < 0)
@ -412,7 +576,7 @@ text_modify(clicon_handle h,
goto fail; goto fail;
} }
if (changed){ if (changed){
if (xml_insert(x0p, x0) < 0) if (xml_insert(x0p, x0, insert, keystr) < 0)
goto done; goto done;
} }
break; break;

View file

@ -625,6 +625,7 @@ xml_child_nr_type(cxobj *xn,
* @retval xml The child xml node * @retval xml The child xml node
* @retval NULL if no such child, or empty child * @retval NULL if no such child, or empty child
* @see xml_child_i_type * @see xml_child_i_type
* @see xml_child_order
*/ */
cxobj * cxobj *
xml_child_i(cxobj *xn, xml_child_i(cxobj *xn,
@ -673,6 +674,29 @@ xml_child_i_set(cxobj *xt,
return 0; 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 /*! Iterator over xml children objects
* *
* @note Never manipulate the child-list during operation or using the * @note Never manipulate the child-list during operation or using the
@ -2375,6 +2399,8 @@ xml_operation(char *opstr,
return 0; return 0;
} }
/*! Map xml operation from enumeration to string /*! Map xml operation from enumeration to string
* @param[in] op enumeration operation, eg OP_MERGE,... * @param[in] op enumeration operation, eg OP_MERGE,...
* @retval str String, eg "merge". Static string, no free necessary * @retval str String, eg "merge". Static string, no free necessary
@ -2406,6 +2432,32 @@ xml_operation2str(enum operation_type op)
return "none"; 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 /*! Specialization of clicon_debug with xml tree
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG.

View file

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

View file

@ -61,6 +61,8 @@
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_options.h" #include "clixon_options.h"
#include "clixon_xml_map.h" #include "clixon_xml_map.h"
#include "clixon_yang_type.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 /*! 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 * static cxobj *
xml_search_userorder(cxobj *xp, xml_search_userorder(cxobj *xp,
cxobj *x1, cxobj *x1,
yang_stmt *y,
int yangi, int yangi,
int mid) int mid)
{ {
int i; int i;
cxobj *xc; cxobj *xc;
yang_stmt *yc;
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */ for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
xc = xml_child_i(xp, i); xc = xml_child_i(xp, i);
y = xml_spec(xc); yc = xml_spec(xc);
if (yangi!=yang_order(y)) if (yangi!=yang_order(yc))
break; break;
if (xml_cmp(xc, x1, 0) == 0) if (xml_cmp(xc, x1, 0) == 0)
return xc; return xc;
} }
for (i=mid-1; i>=0; i--){ /* Then decrement */ for (i=mid-1; i>=0; i--){ /* Then decrement */
xc = xml_child_i(xp, i); xc = xml_child_i(xp, i);
y = xml_spec(xc); yc = xml_spec(xc);
if (yangi!=yang_order(y)) if (yangi!=yang_order(yc))
break; break;
if (xml_cmp(xc, x1, 0) == 0) if (xml_cmp(xc, x1, 0) == 0)
return xc; return xc;
@ -427,7 +434,7 @@ xml_search1(cxobj *xp,
if (cmp == 0){ if (cmp == 0){
cmp = xml_cmp(x1, xc, 0); cmp = xml_cmp(x1, xc, 0);
if (cmp && userorder) /* Ordered by user (if not equal) */ 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) if (cmp == 0)
return xc; return xc;
@ -472,25 +479,123 @@ xml_search(cxobj *xp,
return xret; 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 /*! Insert xn in xp:s sorted child list
* Find a point in xp childvec with two adjacent nodes xi,xi+1 such that * 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 * 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 static int
xml_insert2(cxobj *xp, xml_insert2(cxobj *xp,
cxobj *xn, cxobj *xn,
yang_stmt *yn, yang_stmt *yn,
int yni, int yni,
int userorder, int userorder,
int low, enum insert_type ins,
int upper) char *key_val,
int low,
int upper)
{ {
int retval = -1; int retval = -1;
int mid; int mid;
int cmp; int cmp;
cxobj *xc; cxobj *xc;
yang_stmt *yc; yang_stmt *yc;
int i;
if (low > upper){ /* beyond range */ if (low > upper){ /* beyond range */
clicon_err(OE_XML, 0, "low>upper %d %d", low, upper); 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 (yc == yn){ /* Same yang */
if (userorder){ /* append: increment linearly until no longer equal */ if (userorder){ /* append: increment linearly until no longer equal */
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */ retval = xml_insert_userorder(xp, xn, yn, mid, ins, key_val);
xc = xml_child_i(xp, i);
yc = xml_spec(xc);
if (yc != yn){
retval = i;
goto done;
}
}
retval = i;
goto done; goto done;
} }
else /* Ordered by system */ else /* Ordered by system */
@ -546,23 +643,27 @@ xml_insert2(cxobj *xp,
goto done; goto done;
} }
else if (cmp < 0) 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 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: done:
return retval; return retval;
} }
/*! Insert xc as child to xp in sorted place. Remove xc from previous parent. /*! 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] xp Parent xml node. If NULL just remove from old parent.
* @param[in] x Child xml node to insert under xp * @param[in] x Child xml node to insert under xp
* @retval 0 OK * @param[in] ins Insert operation (if ordered-by user)
* @retval -1 Error * @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() * @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
*/ */
int int
xml_insert(cxobj *xp, xml_insert(cxobj *xp,
cxobj *xi) cxobj *xi,
enum insert_type ins,
char *key_val)
{ {
int retval = -1; int retval = -1;
cxobj *xa; 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) else if (yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST)
userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL); userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL);
yi = yang_order(y); 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; goto done;
if (xml_child_insert_pos(xp, xi, i) < 0) if (xml_child_insert_pos(xp, xi, i) < 0)
goto done; goto done;

View file

@ -5,6 +5,7 @@
# The ordered-by user MUST be the order it is entered. # The ordered-by user MUST be the order it is entered.
# No test of ordered-by system is done yet # No test of ordered-by system is done yet
# (we may want to sort them alphabetically for better performance). # (we may want to sort them alphabetically for better performance).
# Also: ordered-by-user and "insert" and "key"/"value" attributes
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -286,6 +287,108 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></
new "check list decimal64 order (1,2,10)" new "check list decimal64 order (1,2,10)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/exo:types/exo:listdecs" xmlns:exo="urn:example:order"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><types xmlns="urn:example:order"><listdecs><a>1.0</a></listdecs><listdecs><a>2.0</a></listdecs><listdecs><a>10.0</a></listdecs></types></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/exo:types/exo:listdecs" xmlns:exo="urn:example:order"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><types xmlns="urn:example:order"><listdecs><a>1.0</a></listdecs><listdecs><a>2.0</a></listdecs><listdecs><a>10.0</a></listdecs></types></data></rpc-reply>]]>]]>$'
new "delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>none</default-operation><config operation="delete"/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# leaf-list ordered-by-user, "insert" and "value" attributes
# y0 is leaf-list ordered by user
new "add one entry (c) to leaf-list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order">c</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (a) to leaf-list first (with no yang namespace - error)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order" yang:insert="first">a</y0></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-attribute</error-tag><error-info>insert</error-info><error-severity>error</error-severity><error-message>Unresolved attribute prefix (no namespace?)</error-message></rpc-error></rpc-reply>]]>]]>$'
new "add one entry (b) to leaf-list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first">b</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (d) to leaf-list last"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="last">d</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (a) to leaf-list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first">a</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (e) to leaf-list last"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="last">e</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "check ordered-by-user: a,b,c,d,e"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y0 xmlns="urn:example:order">a</y0><y0 xmlns="urn:example:order">b</y0><y0 xmlns="urn:example:order">c</y0><y0 xmlns="urn:example:order">d</y0><y0 xmlns="urn:example:order">e</y0></data></rpc-reply>]]>]]>$'
new "move one entry (e) to leaf-list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y0 operation="replace" xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first">e</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "check ordered-by-user: e,a,b,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y0 xmlns="urn:example:order">e</y0><y0 xmlns="urn:example:order">a</y0><y0 xmlns="urn:example:order">b</y0><y0 xmlns="urn:example:order">c</y0><y0 xmlns="urn:example:order">d</y0></data></rpc-reply>]]>]]>$'
# before and after and value attribute
new "add one leaf-list entry 71 before b"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"before\" yang:value=\"y0[.='b']\">71</y0></config></edit-config></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry 42 after b"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"y0[.='b']\">42</y0></config></edit-config></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry 99 after Q (not found, error)"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"y0[.='Q']\">99</y0></config></edit-config></rpc>]]>]]>"
RES="^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: value, missing-instance: y0\[.='Q'\]</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "$RES"
new "check ordered-by-user: e,a,71,b,42,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y0 xmlns="urn:example:order">e</y0><y0 xmlns="urn:example:order">a</y0><y0 xmlns="urn:example:order">71</y0><y0 xmlns="urn:example:order">b</y0><y0 xmlns="urn:example:order">42</y0><y0 xmlns="urn:example:order">c</y0><y0 xmlns="urn:example:order">d</y0></data></rpc-reply>]]>]]>$'
new "delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>none</default-operation><config operation="delete"/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# list ordered-by-user, "insert" and "value" attributes
# y2 is list ordered by user
new "add one entry (key c) to list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order"><k>c</k><a>foo</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (key a) to list first (with no yang namespace - error)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" yang:insert="first"><k>a</k><a>foo</a></y2></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-attribute</error-tag><error-info>insert</error-info><error-severity>error</error-severity><error-message>Unresolved attribute prefix (no namespace?)</error-message></rpc-error></rpc-reply>]]>]]>$'
new "add one entry (key b) to list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first"><k>b</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (d) to list last"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="last"><k>d</k><a>fie</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (a) to list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first"><k>a</k><a>foo</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry (e) to list last"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="last"><k>e</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "check ordered-by-user: a,b,c,d,e"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>a</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>d</k><a>fie</a></y2><y2 xmlns="urn:example:order"><k>e</k><a>bar</a></y2></data></rpc-reply>]]>]]>$'
new "move one entry (e) to list first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order" xmlns:yang="urn:ietf:params:xml:ns:yang:1" yang:insert="first"><k>e</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "check ordered-by-user: e,a,b,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>e</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>d</k><a>fie</a></y2></data></rpc-reply>]]>]]>$'
# before and after and key attribute
new "add one entry 71 before key b"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><y2 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"before\" yang:key=\"[k='b']\"><k>71</k><a>fie</a></y2></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry 42 after key b"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><y2 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:key=\"[k='b']\"><k>42</k><a>fum</a></y2></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry key 99 after Q (not found, error)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><y2 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:key=\"[k='Q']\"><k>99</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: key, missing-instance: y2\[k='Q'\]</error-message></rpc-error></rpc-reply>]]>]]>$"
new "check ordered-by-user: e,a,71,b,42,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>e</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>71</k><a>fie</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>42</k><a>fum</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>d</k><a>fie</a></y2></data></rpc-reply>]]>]]>$'
if [ $BE -eq 0 ]; then if [ $BE -eq 0 ]; then
exit # BE exit # BE
fi fi

View file

@ -128,8 +128,8 @@ if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"
fi fi
new "restconf get data/ json" new "restconf get data type json"
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":["42","41","43"]} expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}
' '
new "restconf get state operation" new "restconf get state operation"
@ -142,7 +142,7 @@ if [ -z "$match" ]; then
fi fi
new "restconf get state operation type json" new "restconf get state operation type json"
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":["42","41","43"]} expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}
' '
new "restconf get state operation type xml" new "restconf get state operation type xml"

View file

@ -282,6 +282,11 @@ cat <<EOF > $fyang
} }
} }
} }
leaf-list extra{
type string;
ordered-by user;
description "Extra added to test ordered-by user inserts on leaf-lists";
}
} }
EOF EOF
@ -344,23 +349,84 @@ expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' htt
new "4.5. PUT create new identity" new "4.5. PUT create new identity"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new "B.2.4. Replace a Datastore Resource"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 "HTTP/1.1 201 Created"
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert" Parameter'
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
new 'B.3.4. "insert" Parameter first (RFC example says after)'
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
new 'B.3.4. "insert" Parameter check order'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "point" check order (0,2,1)'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
#XXX 'Location: https://example.com/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "point" check order (0,2,3,1)'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>3</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created"
new 'B.3.4. "insert/point" leaf-list 2 first'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created"
new 'B.3.4. "insert/point" leaf-list 1 last'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
#new 'B.3.4. "insert/point" move leaf-list 1 last'
#- restconf cannot move a leaf-list(list?) item
#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
new 'B.3.5. "point" Parameter leaf-list 4 before 3'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
if false; then # NYI if false; then # NYI
new "B.2.2. Detect Datastore Resource Entity-Tag Change" new "B.2.2. Detect Datastore Resource Entity-Tag Change"
new "B.2.3. Edit a Datastore Resource" new "B.2.3. Edit a Datastore Resource"
new "B.2.4. Replace a Datastore Resource"
new "B.2.5. Edit a Data Resource" new "B.2.5. Edit a Data Resource"
new 'B.3.1. "content" Parameter' new 'B.3.1. "content" Parameter'
new 'B.3.2. "depth" Parameter' new 'B.3.2. "depth" Parameter'
new 'B.3.3. "fields" Parameter' new 'B.3.3. "fields" Parameter'
new 'B.3.4. "insert" Parameter'
new 'B.3.5. "point" Parameter'
new 'B.3.6. "filter" Parameter' new 'B.3.6. "filter" Parameter'
new 'B.3.7. "start-time" Parameter' new 'B.3.7. "start-time" Parameter'
new 'B.3.8. "stop-time" Parameter' new 'B.3.8. "stop-time" Parameter'
new 'B.3.9. "with-defaults" Parameter' new 'B.3.9. "with-defaults" Parameter'
fi
fi # NYI
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -185,7 +185,7 @@ main(int argc, char **argv)
clicon_debug(1, "xi:"); clicon_debug(1, "xi:");
xml_print(stderr, xi); xml_print(stderr, xi);
} }
if (xml_insert(xb, xi) < 0) if (xml_insert(xb, xi, INS_LAST, NULL) < 0)
goto done; goto done;
if (debug){ if (debug){
clicon_debug(1, "x0:"); clicon_debug(1, "x0:");