diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dddc838..0ea76a9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Major New features * Restconf RFC 8040 increased feature compliance + * RESTCONF "insert" and "point" query parameters supported * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns: * `201 Created` for created resources * `204 No Content` for replaced resources. @@ -14,6 +15,8 @@ * HTTP `Location:` fields added in RESTCONF POST replies * HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5) * 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 * New plugin callback: ca_extension * 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 ### 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) * Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error. diff --git a/README.md b/README.md index 9fb894e8..fac2ee1e 100644 --- a/README.md +++ b/README.md @@ -235,15 +235,16 @@ Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available t run with NGINX. 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 -- stream notifications (RFC8040 sec 6) -- query parameters start-time and stop-time(RFC8040 section 4.9) +- stream notifications (Sec 6) +- query parameters: "insert", "point", "start-time" and "stop-time". +- Monitoring (Sec 9) The following features are not implemented: - ETag/Last-Modified - PATCH -- query parameters other than start/stop-time. +- Query parameters: "content", "depth", "fields", "filter", "with-defaults" See [more detailed instructions](apps/restconf/README.md). diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index e4d507f3..7f28986d 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -305,6 +305,7 @@ printparam(FCGX_Request *r, /*! Print all FCGI headers * @param[in] r Fastcgi request handle + * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https */ int restconf_test(FCGX_Request *r, @@ -552,3 +553,96 @@ restconf_terminate(clicon_handle h) 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; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 7270807e..6fe968e1 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -63,5 +63,7 @@ int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, int use_xml, int code); int http_location(FCGX_Request *r, cxobj *xobj); int restconf_terminate(clicon_handle h); +int restconf_insert_attributes(cxobj *xdata, cvec *qvec); +char *restconf_uripath(FCGX_Request *r); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 5a7da6b7..6f495f89 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -345,7 +345,7 @@ api_restconf(clicon_handle h, cxobj *xerr; clicon_debug(1, "%s", __FUNCTION__); - path = FCGX_GetParam("REQUEST_URI", r->envp); + path = restconf_uripath(r); query = FCGX_GetParam("QUERY_STRING", r->envp); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); /* get xml/json in put and output */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index ed30ddda..0657920b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -418,7 +418,6 @@ api_data_put(clicon_handle h, op = OP_CREATE; if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; - /* Top-of tree, no api-path * Replace xparent with x, ie bottom of api-path with data */ @@ -522,6 +521,9 @@ api_data_put(clicon_handle h, 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 ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){ diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index e50b25ec..265441b5 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -132,9 +132,8 @@ api_data_post(clicon_handle h, int nullspec = 0; int ret; - clicon_debug(1, "%s api_path:\"%s\" data:\"%s\"", - __FUNCTION__, - api_path, data); + clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path); + clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -164,6 +163,15 @@ api_data_post(clicon_handle h, 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 */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata0) < 0){ @@ -224,6 +232,7 @@ api_data_post(clicon_handle h, goto ok; } xdata = xml_child_i(xdata0,0); + /* If the api-path (above) defines a module, then xdata must have a prefix * and it match the module defined in api-path. * In a POST, maybe there are cornercases where xdata (which is a child) and @@ -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 */ if ((cbx = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "cbuf_new"); diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 3b33d506..92d0b9c2 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -366,7 +366,7 @@ api_stream(clicon_handle h, #endif clicon_debug(1, "%s", __FUNCTION__); - path = FCGX_GetParam("DOCUMENT_URI", r->envp); + path = restconf_uripath(r); query = FCGX_GetParam("QUERY_STRING", r->envp); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); restconf_test(r, 1); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 15a181ba..caf6e424 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -51,7 +51,7 @@ * Types */ /* Netconf operation type */ -enum operation_type{ /* edit-configo */ +enum operation_type{ /* edit-config operation */ OP_MERGE, /* merge config-data */ OP_REPLACE,/* replace or create config-data */ OP_CREATE, /* create config data, error if exist */ @@ -60,6 +60,14 @@ enum operation_type{ /* edit-configo */ 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, CX_ELMNT, 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_type(cxobj *xn, int i, enum cxobj_type type); 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); 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_operation(char *opstr, 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 int clicon_log_xml(int level, cxobj *x, char *format, ...) __attribute__ ((format (printf, 3, 4))); #else diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index d8a9423e..e3b98d69 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -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 xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); - int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1, cxobj ***first, size_t *firstlen, cxobj ***second, size_t *secondlen, diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index a75c1358..4c74cad7 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -42,7 +42,7 @@ 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_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 match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index a0bd8498..410c79a1 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -62,7 +62,7 @@ enum rfc_6020{ Y_BELONGS_TO, Y_BIT, Y_CASE, - Y_CHOICE, + Y_CHOICE, /* 10 */ Y_CONFIG, Y_CONTACT, Y_CONTAINER, @@ -72,7 +72,7 @@ enum rfc_6020{ Y_DEVIATION, Y_ENUM, Y_ERROR_APP_TAG, - Y_ERROR_MESSAGE, + Y_ERROR_MESSAGE, /* 20 */ Y_EXTENSION, Y_FEATURE, Y_FRACTION_DIGITS, @@ -82,17 +82,17 @@ enum rfc_6020{ Y_IMPORT, Y_INCLUDE, Y_INPUT, - Y_KEY, + Y_KEY, /* 30 */ Y_LEAF, Y_LEAF_LIST, Y_LENGTH, - Y_LIST, + Y_LIST, Y_MANDATORY, Y_MAX_ELEMENTS, Y_MIN_ELEMENTS, Y_MODIFIER, Y_MODULE, - Y_MUST, + Y_MUST, /* 40 */ Y_NAMESPACE, Y_NOTIFICATION, Y_ORDERED_BY, @@ -102,7 +102,7 @@ enum rfc_6020{ Y_PATTERN, Y_POSITION, Y_PREFIX, - Y_PRESENCE, + Y_PRESENCE, /* 50 */ Y_RANGE, Y_REFERENCE, Y_REFINE, @@ -112,7 +112,7 @@ enum rfc_6020{ Y_RPC, Y_STATUS, Y_SUBMODULE, - Y_TYPE, + Y_TYPE, /* 60 */ Y_TYPEDEF, Y_UNIQUE, Y_UNITS, diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 7986b50d..9adeff20 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -122,6 +122,55 @@ replace_xmlns(cxobj *x0, return retval; } +/*! Given an attribute name and its expected namespace, find its value + * + * An attribute may have a prefix(or NULL). The routine finds the associated + * xmlns binding to find the namespace: :. + * If such an attribute is not found, failure is returned with cbret set, + * If such an attribute its found, its string value is returned. + * @param[in] x XML node (where to look for attribute) + * @param[in] name Attribute name + * @param[in] namespace (Expected)Namespace of attribute + * @param[out] cbret Error message (if retval=0) + * @param[out] valp Pointer to value (if retval=1) + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK + */ +static int +attr_ns_value(cxobj *x, + char *name, + char *namespace, + cbuf *cbret, + char **valp) +{ + int retval = -1; + cxobj *xa; + char *ans = NULL; /* attribute namespace */ + char *val = NULL; + + /* prefix=NULL since we do not know the prefix */ + if ((xa = xml_find_type(x, NULL, name, CX_ATTR)) != NULL){ + if (xml2ns(xa, xml_prefix(xa), &ans) < 0) + goto done; + if (ans == NULL){ /* the attribute exists, but no namespace */ + if (netconf_bad_attribute(cbret, "application", name, "Unresolved attribute prefix (no namespace?)") < 0) + goto done; + goto fail; + } + /* the attribute exists, but not w expected namespace */ + if (strcmp(ans, namespace) == 0) + val = xml_value(xa); + } + *valp = val; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + /*! Modify a base tree x0 with x1 with yang spec y according to operation op * @param[in] th Datastore text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) @@ -138,6 +187,10 @@ replace_xmlns(cxobj *x0, * @retval 1 OK * Assume x0 and x1 are same on entry and that y is the spec * @see text_modify_top + * RFC 7950 Sec 7.7.9(leaf-list), 7.8.6(lists) + * In an "ordered-by user" list, the attributes "insert" and "key" in + * the YANG XML namespace can be used to control where + * in the list the entry is inserted. */ static int text_modify(clicon_handle h, @@ -152,7 +205,7 @@ text_modify(clicon_handle h, cbuf *cbret) { int retval = -1; - char *opstr; + char *opstr = NULL; char *x1name; char *x1cname; /* child name */ cxobj *x0a; /* attribute */ @@ -167,22 +220,66 @@ text_modify(clicon_handle h, cxobj **x0vec = NULL; int i; int ret; + char *instr = NULL; + char *keystr = NULL; + char *valstr = NULL; + enum insert_type insert = INS_LAST; int changed = 0; /* Only if x0p's children have changed-> sort is necessary */ - + /* Check for operations embedded in tree according to netconf */ +#ifdef notyet /* XXX breaks in test_cohoice.sh */ + if ((ret = attr_ns_value(x1, + "operation", "urn:ietf:params:xml:ns:netconf:base:1.0", + cbret, &opstr)) < 0) + goto done; + if (ret == 0) + goto fail; + if (opstr != NULL) + if (xml_operation(opstr, &op) < 0) + goto done; +#else if ((opstr = xml_find_value(x1, "operation")) != NULL) if (xml_operation(opstr, &op) < 0) goto done; +#endif + x1name = xml_name(x1); - if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){ - /* This is a check on no further elements as a sanity check for eg - * ab - */ + if (yang_keyword_get(y0) == Y_LEAF_LIST || + yang_keyword_get(y0) == Y_LEAF){ + /* This is a check that a leaf does not have sub-elements + * such as: a b + */ if (xml_child_nr_type(x1, CX_ELMNT)){ if (netconf_unknown_element(cbret, "application", x1name, "Leaf contains sub-element") < 0) goto done; goto fail; } + /* If leaf-list and ordered-by user, then get yang:insert attribute + * See RFC 7950 Sec 7.7.9 + */ + if (yang_keyword_get(y0) == Y_LEAF_LIST && + yang_find(y0, Y_ORDERED_BY, "user") != NULL){ + if ((ret = attr_ns_value(x1, + "insert", "urn:ietf:params:xml:ns:yang:1", + cbret, &instr)) < 0) + goto done; + if (ret == 0) + goto fail; + if (instr != NULL && + xml_attr_insert2val(instr, &insert) < 0) + goto done; + if ((ret = attr_ns_value(x1, + "value", "urn:ietf:params:xml:ns:yang:1", + cbret, &valstr)) < 0) + goto done; + /* if insert/before, value attribute must be there */ + if ((insert == INS_AFTER || insert == INS_BEFORE) && + valstr == NULL){ + if (netconf_missing_attribute(cbret, "application", "value", "Missing value attribute when insert is before or after") < 0) + goto done; + goto fail; + } + } x1bstr = xml_body(x1); switch(op){ case OP_CREATE: @@ -191,9 +288,29 @@ text_modify(clicon_handle h, goto done; goto fail; } - case OP_NONE: /* fall thru */ + case OP_REPLACE: /* fall thru */ case OP_MERGE: - case OP_REPLACE: + if (!(op == OP_MERGE && instr==NULL)){ + /* Remove existing, also applies to merge in the special case + * of ordered-by user and (changed) insert attribute. + */ + if (!permit && xnacm){ + if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + permit = 1; + } + /* XXX: Note, if there is an error in adding the object later, the + * original object is not reverted. + */ + if (x0){ + xml_purge(x0); + x0 = NULL; + } + } /* OP_MERGE & insert */ + case OP_NONE: /* fall thru */ + if (x0==NULL){ if ((op != OP_NONE) && !permit && xnacm){ if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) @@ -208,11 +325,15 @@ text_modify(clicon_handle h, goto done; changed++; - /* Copy xmlns attributes */ + /* Copy xmlns attributes ONLY, not op/insert etc */ x1a = NULL; while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL) if (strcmp(xml_name(x1a),"xmlns")==0 || ((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){ +#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */ + if (strcmp(xml_value(x1a),"urn:ietf:params:xml:ns:yang:1")==0) + continue; +#endif if ((x0a = xml_dup(x1a)) == NULL) goto done; if (xml_addsub(x0, x0a) < 0) @@ -261,8 +382,8 @@ text_modify(clicon_handle h, } } } - if (changed){ - if (xml_insert(x0p, x0) < 0) + if (changed){ + if (xml_insert(x0p, x0, insert, valstr) < 0) goto done; } break; @@ -289,6 +410,37 @@ text_modify(clicon_handle h, } /* switch op */ } /* if LEAF|LEAF_LIST */ else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */ + /* If list and ordered-by user, then get insert attribute + + * 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", "key", "Missing key attribute when insert is before or after") < 0) + goto done; + goto fail; + } + + } switch(op){ case OP_CREATE: if (x0){ @@ -297,19 +449,27 @@ text_modify(clicon_handle h, goto fail; } case OP_REPLACE: /* fall thru */ - if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; - permit = 1; - } - if (x0){ - xml_purge(x0); - x0 = NULL; - } - case OP_MERGE: /* fall thru */ - case OP_NONE: + case OP_MERGE: + if (!(op == OP_MERGE && instr==NULL)){ + /* Remove existing, also applies to merge in the special case + * of ordered-by user and (changed) insert attribute. + */ + if (!permit && xnacm){ + if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + permit = 1; + } + /* XXX: Note, if there is an error in adding the object later, the + * original object is not reverted. + */ + if (x0){ + xml_purge(x0); + x0 = NULL; + } + } /* OP_MERGE & insert */ + case OP_NONE: /* fall thru */ /* Special case: anyxml, just replace tree, See rfc6020 7.10.3:n An anyxml node is treated as an opaque chunk of data. This data @@ -356,6 +516,10 @@ text_modify(clicon_handle h, while ((x1a = xml_child_each(x1, x1a, CX_ATTR)) != NULL) if (strcmp(xml_name(x1a),"xmlns")==0 || ((xns = xml_prefix(x1a)) && strcmp(xns, "xmlns")==0)){ +#if 1 /* XXX Kludge to NOT copy RFC7950 xmlns:yang insert/key/value namespaces */ + if (strcmp(xml_value(x1a),"urn:ietf:params:xml:ns:yang:1")==0) + continue; +#endif if ((x0a = xml_dup(x1a)) == NULL) goto done; if (xml_addsub(x0, x0a) < 0) @@ -412,7 +576,7 @@ text_modify(clicon_handle h, goto fail; } if (changed){ - if (xml_insert(x0p, x0) < 0) + if (xml_insert(x0p, x0, insert, keystr) < 0) goto done; } break; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index e7d7e371..ccb31ab6 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -625,6 +625,7 @@ xml_child_nr_type(cxobj *xn, * @retval xml The child xml node * @retval NULL if no such child, or empty child * @see xml_child_i_type + * @see xml_child_order */ cxobj * xml_child_i(cxobj *xn, @@ -673,6 +674,29 @@ xml_child_i_set(cxobj *xt, return 0; } +/*! Get the order of child + * @param[in] xp xml parent node + * @param[in] xc the xml child to look for + * @retval xml The child xml node + * @retval i The order of the child + * @retval -1 if no such child, or empty child + * @see xml_child_i + */ +int +xml_child_order(cxobj *xp, + cxobj *xc) +{ + cxobj *x = NULL; + int i = 0; + + while ((x = xml_child_each(xp, x, -1)) != NULL) { + if (x == xc) + return i; + i++; + } + return -1; +} + /*! Iterator over xml children objects * * @note Never manipulate the child-list during operation or using the @@ -2375,6 +2399,8 @@ xml_operation(char *opstr, return 0; } + + /*! Map xml operation from enumeration to string * @param[in] op enumeration operation, eg OP_MERGE,... * @retval str String, eg "merge". Static string, no free necessary @@ -2406,6 +2432,32 @@ xml_operation2str(enum operation_type op) return "none"; } } +/*! Map xml insert attribute from string to enumeration + * @param[in] instr String, eg "first" + * @param[out] ins Enumeration, eg INS_FIRST + * @code + * enum insert_type ins; + * xml_operation("last", &ins) + * @endcode + */ +int +xml_attr_insert2val(char *instr, + enum insert_type *ins) +{ + if (strcmp("first", instr) == 0) + *ins = INS_FIRST; + else if (strcmp("last", instr) == 0) + *ins = INS_LAST; + else if (strcmp("before", instr) == 0) + *ins = INS_BEFORE; + else if (strcmp("after", instr) == 0) + *ins = INS_AFTER; + else{ + clicon_err(OE_XML, 0, "Bad-attribute operation: %s", instr); + return -1; + } + return 0; +} /*! Specialization of clicon_debug with xml tree * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index afb18f09..3e395a4d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2198,7 +2198,7 @@ xml_default(cxobj *xt, goto done; free(str); added++; - if (xml_insert(xt, xc) < 0) + if (xml_insert(xt, xc, INS_LAST, NULL) < 0) goto done; } } @@ -2455,21 +2455,33 @@ api_path2xpath_cvv(cvec *api_path, if (cv2str(cv, NULL, 0) > 0){ if ((val = cv2str_dup(cv)) == NULL) goto done; - v = val; - /* XXX sync with yang */ - while((v=index(v, ',')) != NULL){ - *v = '\0'; - v++; - } - cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - /* Iterate over individual yang keys */ - - cprintf(xpath, "/%s", name); - v = val; - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v); - v += strlen(v)+1; + switch (yang_keyword_get(y)){ + case Y_LIST: + v = val; + while((v=index(v, ',')) != NULL){ + *v = '\0'; + v++; + } + cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ + cvi = NULL; + /* Iterate over individual yang keys */ + cprintf(xpath, "/%s", name); + v = val; + while ((cvi = cvec_each(cvk, cvi)) != NULL){ + cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v); + v += strlen(v)+1; + } + break; + case Y_LEAF_LIST: /* XXX: LOOP? */ + cprintf(xpath, "/%s", name); + if (val) + cprintf(xpath, "[.='%s']", val); + else + cprintf(xpath, "[.='']"); + break; + default: + cprintf(xpath, "%s%s", (i==offset?"":"/"), name); + break; } if (val) free(val); @@ -2516,7 +2528,7 @@ api_path2xpath_cvv(cvec *api_path, * ... access xpath as cbuf_get(xpath) * free(xpath) * @endcode - + * * @see api_path2xml_cvv which uses other parameter formats */ int diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 30a1f332..ba9b8435 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -61,6 +61,8 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_options.h" #include "clixon_xml_map.h" #include "clixon_yang_type.h" @@ -361,30 +363,35 @@ xml_sort(cxobj *x, } /*! Special case search for ordered-by user where linear sort is used + * @param[in] xp Parent XML node (go through its childre) + * @param[in] x1 XML node to match + * @param[in] yangi Yang order number (according to spec) + * @param[in] mid Where to start from (may be in middle of interval) + * @retval NULL Not found + * @retval x XML element that matches x1 */ static cxobj * xml_search_userorder(cxobj *xp, cxobj *x1, - yang_stmt *y, int yangi, int mid) - { - int i; - cxobj *xc; + int i; + cxobj *xc; + yang_stmt *yc; for (i=mid+1; i=0; i--){ /* Then decrement */ xc = xml_child_i(xp, i); - y = xml_spec(xc); - if (yangi!=yang_order(y)) + yc = xml_spec(xc); + if (yangi!=yang_order(yc)) break; if (xml_cmp(xc, x1, 0) == 0) return xc; @@ -427,7 +434,7 @@ xml_search1(cxobj *xp, if (cmp == 0){ cmp = xml_cmp(x1, xc, 0); if (cmp && userorder) /* Ordered by user (if not equal) */ - return xml_search_userorder(xp, x1, y, yangi, mid); + return xml_search_userorder(xp, x1, yangi, mid); } if (cmp == 0) return xc; @@ -472,25 +479,123 @@ xml_search(cxobj *xp, return xret; } +/*! Insert xn in xp:s sorted child list (special case of ordered-by user) + * @param[in] xp Parent xml node. If NULL just remove from old parent. + * @param[in] xn Child xml node to insert under xp + * @param[in] yn Yang stmt of xml child node + * @param[in] ins Insert operation (if ordered-by user) + * @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST + * @retval i Order where xn should be inserted into xp:s children + * @retval -1 Error + */ + +static int +xml_insert_userorder(cxobj *xp, + cxobj *xn, + yang_stmt *yn, + int mid, + enum insert_type ins, + char *key_val) +{ + int retval = -1; + int i; + cxobj *xc; + yang_stmt *yc; + char *kludge = ""; /* Cant get instance-id generic of [.. and /.. case */ + + switch (ins){ + case INS_FIRST: + for (i=mid-1; i>=0; i--){ /* decrement */ + xc = xml_child_i(xp, i); + yc = xml_spec(xc); + if (yc != yn){ + retval = i+1; + goto done; + } + } + retval = i+1; + break; + case INS_LAST: + for (i=mid+1; i upper){ /* beyond range */ clicon_err(OE_XML, 0, "low>upper %d %d", low, upper); @@ -512,15 +617,7 @@ xml_insert2(cxobj *xp, } if (yc == yn){ /* Same yang */ if (userorder){ /* append: increment linearly until no longer equal */ - for (i=mid+1; i]]>]]>' '^1.02.010.0]]>]]>$' +new "delete candidate" +expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# 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 'c]]>]]>' "^]]>]]>$" + +new "add one entry (a) to leaf-list first (with no yang namespace - error)" +expecteof "$clixon_netconf -qf $cfg" 0 'a]]>]]>' '^applicationbad-attributeinserterrorUnresolved attribute prefix (no namespace?)]]>]]>$' + +new "add one entry (b) to leaf-list first" +expecteof "$clixon_netconf -qf $cfg" 0 'b]]>]]>' "^]]>]]>$" + +new "add one entry (d) to leaf-list last" +expecteof "$clixon_netconf -qf $cfg" 0 'd]]>]]>' "^]]>]]>$" + +new "add one entry (a) to leaf-list first" +expecteof "$clixon_netconf -qf $cfg" 0 'a]]>]]>' "^]]>]]>$" + +new "add one entry (e) to leaf-list last" +expecteof "$clixon_netconf -qf $cfg" 0 'e]]>]]>' "^]]>]]>$" + +new "check ordered-by-user: a,b,c,d,e" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^abcde]]>]]>$' + +new "move one entry (e) to leaf-list first" +expecteof "$clixon_netconf -qf $cfg" 0 'e]]>]]>' "^]]>]]>$" + +new "check ordered-by-user: e,a,b,c,d" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eabcd]]>]]>$' + +# before and after and value attribute +new "add one leaf-list entry 71 before b" +XML="71]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^]]>]]>$" + +new "add one entry 42 after b" +XML="42]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^]]>]]>$" + +# XXX actually not right error message, should be as RFC7950 Sec 15.7 +new "add one entry 99 after Q (not found, error)" +XML="99]]>]]>" +RES="^protocoloperation-failederrorbad-attribute: value, missing-instance: y0\[.='Q'\]]]>]]>$" +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 ']]>]]>' '^ea71b42cd]]>]]>$' + +new "delete candidate" +expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# 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 'cfoo]]>]]>' "^]]>]]>$" + +new "add one entry (key a) to list first (with no yang namespace - error)" +expecteof "$clixon_netconf -qf $cfg" 0 'afoo]]>]]>' '^applicationbad-attributeinserterrorUnresolved attribute prefix (no namespace?)]]>]]>$' + +new "add one entry (key b) to list first" +expecteof "$clixon_netconf -qf $cfg" 0 'bbar]]>]]>' "^]]>]]>$" + +new "add one entry (d) to list last" +expecteof "$clixon_netconf -qf $cfg" 0 'dfie]]>]]>' "^]]>]]>$" + +new "add one entry (a) to list first" +expecteof "$clixon_netconf -qf $cfg" 0 'afoo]]>]]>' "^]]>]]>$" + +new "add one entry (e) to list last" +expecteof "$clixon_netconf -qf $cfg" 0 'ebar]]>]]>' "^]]>]]>$" + +new "check ordered-by-user: a,b,c,d,e" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^afoobbarcfoodfieebar]]>]]>$' + +new "move one entry (e) to list first" +expecteof "$clixon_netconf -qf $cfg" 0 'ebar]]>]]>' "^]]>]]>$" + +new "check ordered-by-user: e,a,b,c,d" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^ebarafoobbarcfoodfie]]>]]>$' + +# before and after and key attribute +new "add one entry 71 before key b" +expecteof "$clixon_netconf -qf $cfg" 0 "71fie]]>]]>" "^]]>]]>$" + +new "add one entry 42 after key b" +expecteof "$clixon_netconf -qf $cfg" 0 "42fum]]>]]>" "^]]>]]>$" + +# 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 "99bar]]>]]>" "^protocoloperation-failederrorbad-attribute: key, missing-instance: y2\[k='Q'\]]]>]]>$" + +new "check ordered-by-user: e,a,71,b,42,c,d" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^ebarafoo71fiebbar42fumcfoodfie]]>]]>$' + if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index aa579de2..20ada66d 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -128,8 +128,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get data/ json" -expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":["42","41","43"]} +new "restconf get data type json" +expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} ' new "restconf get state operation" @@ -142,7 +142,7 @@ if [ -z "$match" ]; then fi 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" diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index f36c96d6..702d19ce 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -282,6 +282,11 @@ cat < $fyang } } } + leaf-list extra{ + type string; + ordered-by user; + description "Extra added to test ordered-by user inserts on leaf-lists"; + } } 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" 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 'Foo FightersOne by One2012Nick Cave and the Bad SeedsTender Prey1988')" 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="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" +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="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" +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="Foo-One0/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]2/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]3/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]1/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]" +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' '231' + +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' '2431' if false; then # NYI new "B.2.2. Detect Datastore Resource Entity-Tag Change" 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.3.1. "content" Parameter' new 'B.3.2. "depth" 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.7. "start-time" Parameter' new 'B.3.8. "stop-time" Parameter' new 'B.3.9. "with-defaults" Parameter' -fi + +fi # NYI new "Kill restconf daemon" stop_restconf diff --git a/util/clixon_util_insert.c b/util/clixon_util_insert.c index 7d6f675f..34775f3d 100644 --- a/util/clixon_util_insert.c +++ b/util/clixon_util_insert.c @@ -185,7 +185,7 @@ main(int argc, char **argv) clicon_debug(1, "xi:"); xml_print(stderr, xi); } - if (xml_insert(xb, xi) < 0) + if (xml_insert(xb, xi, INS_LAST, NULL) < 0) goto done; if (debug){ clicon_debug(1, "x0:");