* 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:
parent
d7a8cf5b0c
commit
2d9d204f69
20 changed files with 740 additions and 105 deletions
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,6 +449,11 @@ text_modify(clicon_handle h,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
case OP_REPLACE: /* fall thru */
|
case OP_REPLACE: /* fall thru */
|
||||||
|
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 (!permit && xnacm){
|
||||||
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -304,12 +461,15 @@ text_modify(clicon_handle h,
|
||||||
goto fail;
|
goto fail;
|
||||||
permit = 1;
|
permit = 1;
|
||||||
}
|
}
|
||||||
|
/* XXX: Note, if there is an error in adding the object later, the
|
||||||
|
* original object is not reverted.
|
||||||
|
*/
|
||||||
if (x0){
|
if (x0){
|
||||||
xml_purge(x0);
|
xml_purge(x0);
|
||||||
x0 = NULL;
|
x0 = NULL;
|
||||||
}
|
}
|
||||||
case OP_MERGE: /* fall thru */
|
} /* OP_MERGE & insert */
|
||||||
case OP_NONE:
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,8 +2455,9 @@ 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;
|
||||||
|
switch (yang_keyword_get(y)){
|
||||||
|
case Y_LIST:
|
||||||
v = val;
|
v = val;
|
||||||
/* XXX sync with yang */
|
|
||||||
while((v=index(v, ',')) != NULL){
|
while((v=index(v, ',')) != NULL){
|
||||||
*v = '\0';
|
*v = '\0';
|
||||||
v++;
|
v++;
|
||||||
|
|
@ -2464,13 +2465,24 @@ api_path2xpath_cvv(cvec *api_path,
|
||||||
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
|
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
|
||||||
cvi = NULL;
|
cvi = NULL;
|
||||||
/* Iterate over individual yang keys */
|
/* 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
|
||||||
|
|
|
||||||
|
|
@ -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,9 +479,106 @@ 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,
|
||||||
|
|
@ -482,6 +586,8 @@ xml_insert2(cxobj *xp,
|
||||||
yang_stmt *yn,
|
yang_stmt *yn,
|
||||||
int yni,
|
int yni,
|
||||||
int userorder,
|
int userorder,
|
||||||
|
enum insert_type ins,
|
||||||
|
char *key_val,
|
||||||
int low,
|
int low,
|
||||||
int upper)
|
int upper)
|
||||||
{
|
{
|
||||||
|
|
@ -490,7 +596,6 @@ xml_insert2(cxobj *xp,
|
||||||
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,9 +643,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -556,13 +653,17 @@ xml_insert2(cxobj *xp,
|
||||||
/*! 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
|
||||||
|
* @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 0 OK
|
||||||
* @retval -1 Error
|
* @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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue