From d0660bf61150c4e6b9b443d7fc44a1766183847b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 30 Jul 2017 11:00:02 +0200 Subject: [PATCH 1/3] Added xpath support for predicate: current(); added by-reference return for schema_nodeid functions; prototype enum2int function --- CHANGELOG.md | 1 + lib/clixon/clixon_xml_map.h | 1 + lib/clixon/clixon_yang.h | 7 +- lib/src/clixon_xml_map.c | 51 ++++++ lib/src/clixon_xsl.c | 191 ++++++++++++++-------- lib/src/clixon_yang.c | 305 +++++++++++++++++++----------------- lib/src/clixon_yang_type.c | 1 + test/test_leafref.sh | 7 +- test/test_type.sh | 12 ++ 9 files changed, 368 insertions(+), 208 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940860b8..26b9d196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * You need to define state data in a backend callback. See the example and documentation for more details. ### Minor changes: +* Added xpath support for predicate: current(), eg /interface[name=current()/../name] * Added prefix parsing of xpath, allowing eg /p:x/p:y, but prefix ignored. * Corrected Yang union CLI generation and type validation. Recursive unions did not work. * Corrected Yang pattern type escaping problem, ie '\.' did not work properly. This requires update of cligen as well. diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 36cfda82..bfd27302 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -75,5 +75,6 @@ int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); +int yang_enum_int_value(cxobj *node, int32_t *val); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index fb8d49a8..9593181c 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -213,9 +213,10 @@ int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); -yang_node *yang_abs_schema_nodeid(yang_node *yn, char *schema_nodeid); -yang_node *yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid); -yang_node *yang_schema_nodeid(yang_node *yn, char *xpath); +int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, + yang_stmt **yres); +int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, + yang_stmt **yres); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys); int yang_mandatory(yang_stmt *ys); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index dccf0fa5..449adc23 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1999,3 +1999,54 @@ xml_merge(cxobj *x0, return retval; } +/*! Get integer value from xml node from yang enumeration + * @param[in] node XML node in a tree + * @param[out] val Integer value returned + * @retval 0 OK, value parsed + * @retval -1 Error, value not obtained or parsed, no reason given + * @note assume yang spec set + * @note The function only returns assigned values, but according to RFC: + If a value is not specified, then one will be automatically assigned. + If the "enum" substatement is the first one defined, the assigned + value is zero (0); otherwise, the assigned value is one greater than + the current highest enum value (i.e., the highest enum value, + * Thanks: Matthew Smith + */ +int +yang_enum_int_value(cxobj *node, + int32_t *val) +{ + int retval = -1; + yang_spec *yspec; + yang_stmt *ys; + yang_stmt *ytype; + yang_stmt *yrestype; /* resolved type */ + yang_stmt *yenum; + yang_stmt *yval; + char *reason = NULL; + + if (node == NULL) + goto done; + if ((ys = (yang_stmt *) xml_spec(node)) == NULL) + goto done; + if ((yspec = ys_spec(ys)) == NULL) + goto done; + if ((ytype = yang_find((yang_node *)ys, Y_TYPE, NULL)) == NULL) + goto done; + if (yang_type_resolve(ys, ytype, &yrestype, + NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (yrestype==NULL || strcmp(yrestype->ys_argument, "enumeration")) + goto done; + if ((yenum = yang_find((yang_node *)yrestype, Y_ENUM, xml_body(node))) == NULL) + goto done; + /* Should assign value if yval not found */ + if ((yval = yang_find((yang_node *)yenum, Y_VALUE, NULL)) == NULL) + goto done; + /* reason is string containing why int could not be parsed */ + if (parse_int32(yval->ys_argument, val, &reason) < 0) + goto done; + retval = 0; +done: + return retval; +} diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 0d3146d4..ff21d008 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -37,7 +37,7 @@ * Look at the end of the file for a test unit program */ /* -https://www.w3.org/TR/xpath/ +See https://www.w3.org/TR/xpath/ Implementation of a limited xslt xpath syntax. Some examples. Given the following xml tree: @@ -172,6 +172,9 @@ xpath_print(FILE *f, struct xpath_element *xplist) return 0; } +/*! Extract PredicateExpr (Expr) from a Predicate within [] + * @see xpath_expr For evaluation of predicate + */ static int xpath_parse_predicate(struct xpath_element *xe, char *pred) @@ -439,7 +442,13 @@ recursive_find(cxobj *xn, return retval; } +/* forward */ +static int +xpath_exec(cxobj *xcur, char *xpath, cxobj **vec0, size_t vec0len, + uint16_t flags, cxobj ***vec2, size_t *vec2len); + /*! XPath predicate expression check + * @param[in] xcur xml-tree where to search * @param[in] predicate_expression xpath expression as a string * @param[in] flags Extra xml flag checks that must match (apart from predicate) * @param[in,out] vec0 Vector or xml nodes that are checked. Not matched are filtered @@ -454,7 +463,8 @@ recursive_find(cxobj *xn, * @see https://www.w3.org/TR/xpath/#predicates */ static int -xpath_expr(char *predicate_expression, +xpath_expr(cxobj *xcur, + char *predicate_expression, uint16_t flags, cxobj ***vec0, size_t *vec0len) @@ -462,6 +472,7 @@ xpath_expr(char *predicate_expression, char *e_a; char *e_v; int i; + int j; int retval = -1; cxobj *x; cxobj *xv; @@ -520,25 +531,72 @@ xpath_expr(char *predicate_expression, goto done; } } - else{ + else{ /* name = expr */ if ((tag = strsep(&e, "=")) == NULL){ clicon_err(OE_XML, errno, "%s: malformed expression: [%s]", __FUNCTION__, e); goto done; } - for (i=0; i<*vec0len; i++){ - xv = (*vec0)[i]; - /* Check if more may match,... */ - x = NULL; - while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { - if (strcmp(tag, xml_name(x)) != 0) - continue; - if ((val = xml_body(x)) != NULL && - strcmp(val, e) == 0){ - clicon_debug(2, "%s %x %x", __FUNCTION__, flags, xml_flag(xv, flags)); - if (flags==0x0 || xml_flag(xv, flags)) - if (cxvec_append(xv, &vec, &veclen) < 0) - goto done; + /* Strip trailing spaces */ + while (tag[strlen(tag)-1] == ' ') + tag[strlen(tag)-1] = '\0'; + /* Strip heading spaces */ + while (e[0]==' ') + e++; + if (strncmp(e, "current()", strlen("current()")) == 0){ + /* name = current()xpath */ + cxobj **svec0 = NULL; + size_t svec0len = 0; + cxobj **svec1 = NULL; + size_t svec1len = 0; + char *ebody; + + e += strlen("current("); /* e is path expression */ + *e = '.'; + if ((svec0 = calloc(1, sizeof(cxobj *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + svec0[0] = xcur; + svec0len++; + /* Recursive invocation */ + if (xpath_exec(xcur, e, svec0, svec0len, + flags, &svec1, &svec1len) < 0) + goto done; + for (j=0; jxe_predicate; xp; xp = xp->xp_next){ - if (xpath_expr(xp->xp_expr, flags, &vec0, &vec0len) < 0) + if (xpath_expr(xcur, xp->xp_expr, flags, &vec0, &vec0len) < 0) goto done; } - if (xpath_find(xe->xe_next, descendants, + if (xpath_find(xcur, xe->xe_next, descendants, vec0, vec0len, flags, vec2, vec2len) < 0) goto done; @@ -719,6 +779,7 @@ xpath_split(char *xpathstr, } /*! Process single xpath expression on xml tree + * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[in] vec0 vector of XML trees * @param[in] vec0len length of XML trees @@ -727,7 +788,8 @@ xpath_split(char *xpathstr, * @param[out] vec2len Length of result vector. */ static int -xpath_exec(char *xpath, +xpath_exec(cxobj *xcur, + char *xpath, cxobj **vec0, size_t vec0len, uint16_t flags, @@ -744,7 +806,7 @@ xpath_exec(char *xpath, goto done; if (debug > 1) xpath_print(stderr, xplist); - if (xpath_find(xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0) + if (xpath_find(xcur, xplist, 0, vec1, vec1len, flags, vec2, vec2len) < 0) goto done; if (xpath_free(xplist) < 0) goto done; @@ -754,6 +816,11 @@ xpath_exec(char *xpath, /*! Intermediate xpath function to handle 'conditional' cases. + * @param[in] xcur xml-tree where to search + * @param[in] xpath string with XPATH syntax + * @param[in] flags if != 0, only match xml nodes matching flags + * @param[in] vec1 vector of XML trees + * @param[in] vec1len length of XML trees * For example: xpath = //a | //b. * xpath_first+ splits xpath up in several subcalls * (eg xpath=//a and xpath=//b) and collects the results. @@ -762,7 +829,7 @@ xpath_exec(char *xpath, * Note, this could be 'folded' into xpath1 but I judged it too complex. */ static int -xpath_choice(cxobj *xtop, +xpath_choice(cxobj *xcur, char *xpath0, uint16_t flags, cxobj ***vec1, @@ -776,7 +843,6 @@ xpath_choice(cxobj *xtop, cxobj **vec0 = NULL; size_t vec0len = 0; - if ((s0 = strdup(xpath0)) == NULL){ clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); goto done; @@ -786,7 +852,7 @@ xpath_choice(cxobj *xtop, clicon_err(OE_UNIX, errno, "calloc"); goto done; } - vec0[0] = xtop; + vec0[0] = xcur; vec0len++; while (s1 != NULL){ s2 = strstr(s1, " | "); @@ -796,7 +862,7 @@ xpath_choice(cxobj *xtop, } xpath = s1; s1 = s2; - if (xpath_exec(xpath, vec0, vec0len, flags, vec1, vec1len) < 0) + if (xpath_exec(xcur, xpath, vec0, vec0len, flags, vec1, vec1len) < 0) goto done; } retval = 0; @@ -808,32 +874,35 @@ xpath_choice(cxobj *xtop, return retval; } +/*! Help function to xpath_first + */ static cxobj * -xpath_first0(cxobj *cxtop, - char *xpath) +xpath_first0(cxobj *xcur, + char *xpath) { - cxobj **vec0 = NULL; - size_t vec0len = 0; + cxobj **vec1 = NULL; + size_t vec1len = 0; cxobj *xn = NULL; - if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0) + if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0) goto done; - if (vec0len) - xn = vec0[0]; + if (vec1len) + xn = vec1[0]; else xn = NULL; done: - if (vec0) - free(vec0); + if (vec1) + free(vec1); return xn; } /*! A restricted xpath function where the first matching entry is returned * See xpath1() on details for subset. * args: - * @param[in] cxtop xml-tree where to search + * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax - * @retval xml-tree of first match, or NULL on error. + * @retval xml-tree of first match + * @retval NULL Error or not found * * @code * cxobj *x; @@ -846,7 +915,7 @@ xpath_first0(cxobj *cxtop, * @see also xpath_vec. */ cxobj * -xpath_first(cxobj *cxtop, +xpath_first(cxobj *xcur, char *format, ...) { @@ -871,7 +940,7 @@ xpath_first(cxobj *cxtop, goto done; } va_end(ap); - retval = xpath_first0(cxtop, xpath); + retval = xpath_first0(xcur, xpath); done: if (xpath) free(xpath); @@ -881,14 +950,14 @@ xpath_first(cxobj *cxtop, /*! A restricted xpath iterator that loops over all matching entries. Dont use. * * See xpath1() on details for subset. - * @param[in] cxtop xml-tree where to search + * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[in] xprev iterator/result should be initiated to NULL * @retval xml-tree of n:th match, or NULL on error. * * @code * cxobj *x = NULL; - * while ((x = xpath_each(cxtop, "//symbol/foo", x)) != NULL) { + * while ((x = xpath_each(xcur, "//symbol/foo", x)) != NULL) { * ... * } * @endcode @@ -899,33 +968,33 @@ xpath_first(cxobj *cxtop, * NOTE: uses a static variable: consider replacing with xpath_vec() instead */ cxobj * -xpath_each(cxobj *cxtop, +xpath_each(cxobj *xcur, char *xpath, cxobj *xprev) { - static cxobj **vec0 = NULL; /* XXX */ - static size_t vec0len = 0; + static cxobj **vec1 = NULL; /* XXX */ + static size_t vec1len = 0; cxobj *xn = NULL; int i; if (xprev == NULL){ - if (vec0) // XXX - free(vec0); // XXX - vec0len = 0; - if (xpath_choice(cxtop, xpath, 0, &vec0, &vec0len) < 0) + if (vec1) // XXX + free(vec1); // XXX + vec1len = 0; + if (xpath_choice(xcur, xpath, 0, &vec1, &vec1len) < 0) goto done; } - if (vec0len){ + if (vec1len){ if (xprev==NULL) - xn = vec0[0]; + xn = vec1[0]; else{ - for (i=0; i=vec0len-1) + if (i>=vec1len-1) xn = NULL; else - xn = vec0[i+1]; + xn = vec1[i+1]; } } else @@ -937,7 +1006,7 @@ xpath_each(cxobj *cxtop, /*! A restricted xpath that returns a vector of matches * * See xpath1() on details for subset -. * @param[in] cxtop xml-tree where to search +. * @param[in] xcur xml-tree where to search * @param[in] xpath string with XPATH syntax * @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] veclen returns length of vector in return value @@ -947,7 +1016,7 @@ xpath_each(cxobj *cxtop, * @code * cxobj **xvec; * size_t xlen; - * if (xpath_vec(cxtop, "//symbol/foo", &xvec, &xlen) < 0) + * if (xpath_vec(xcur, "//symbol/foo", &xvec, &xlen) < 0) * goto err; * for (i=0; iyn_len; i++){ - ys = yn->yn_stmt[i]; - if (!yang_schemanode(ys)) - continue; - /* some keys dont have arguments, match on key */ - if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ - if (strcmp(argument, yang_key2str(ys->ys_keyword)) == 0){ - match++; - break; - } - } else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0){ - match++; - break; - } - } - return match ? ys : NULL; -} - /*! Reset flag in complete tree, arg contains flag */ static int ys_flag_reset(yang_stmt *ys, @@ -1074,7 +1039,7 @@ ys_grouping_resolve(yang_stmt *ys, The target node MUST be either a container, list, choice, case, input, output, or notification node. If the "augment" statement is on the top level the absolute form MUST be used. - XXX: Destructively changing a datamodel may affect outlying loop? + @note Destructively changing a datamodel may affect outlying loop? */ static int yang_augment_node(yang_stmt *ys, @@ -1082,7 +1047,7 @@ yang_augment_node(yang_stmt *ys, { int retval = -1; char *schema_nodeid; - yang_node *yn; + yang_stmt *yss = NULL; yang_stmt *yc; int i; @@ -1090,21 +1055,21 @@ yang_augment_node(yang_stmt *ys, clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); /* Find the target */ - if ((yn = yang_abs_schema_nodeid((yang_node*)ys, schema_nodeid)) == NULL){ - clicon_err(OE_YANG, 0, "Augment schema_nodeid %s not found", schema_nodeid); - // retval = 0; /* Ignore, continue */ + if (yang_abs_schema_nodeid(ysp, schema_nodeid, &yss) < 0) goto done; - } - /* Extend yn with ys' children - * First enlarge yn vector + if (yss == NULL) + goto ok; + /* Extend yss with ys' children + * First enlarge yss vector */ for (i=0; iys_len; i++){ if ((yc = ys_dup(ys->ys_stmt[i])) == NULL) goto done; /* XXX: use prefix of origin */ - if (yn_insert(yn, yc) < 0) + if (yn_insert((yang_node*)yss, yc) < 0) goto done; } + ok: retval = 0; done: return retval; @@ -1126,10 +1091,14 @@ yang_augment_spec(yang_spec *ysp) j = 0; while (jys_len){ /* Top-level symbols in modules */ ys = ym->ys_stmt[j++]; - if (ys->ys_keyword != Y_AUGMENT) - continue; - if (yang_augment_node(ys, ysp) < 0) - goto done; + switch (ys->ys_keyword){ + case Y_AUGMENT: /* top-level */ + if (yang_augment_node(ys, ysp) < 0) + goto done; + break; + default: + break; + } } } retval = 0; @@ -1498,6 +1467,49 @@ yang_parse_recurse(clicon_handle h, return ymod; /* top-level (sub)module */ } +int +ys_schemanode_check(yang_stmt *ys, + void *arg) +{ + int retval = -1; + yang_spec *yspec; + yang_stmt *yres; + yang_node *yp; + + yp = ys->ys_parent; + switch (ys->ys_keyword){ + case Y_AUGMENT: + if (yp->yn_keyword == Y_MODULE) /* Not top-level */ + break; + /* fallthru */ + case Y_REFINE: + case Y_UNIQUE: + if (yang_desc_schema_nodeid(yp, ys->ys_argument, &yres) < 0) + goto done; + if (yres == NULL){ + clicon_err(OE_YANG, 0, "schemanode sanity check of %d %s", + ys->ys_keyword, + ys->ys_argument); + goto done; + } + break; + case Y_DEVIATION: + yspec = ys_spec(ys); + if (yang_abs_schema_nodeid(yspec, ys->ys_argument, &yres) < 0) + goto done; + if (yres == NULL){ + clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument); + goto done; + } + break; + default: + break; + } + retval = 0; + done: + return retval; +} + /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * @param[in] h CLICON handle @@ -1552,12 +1564,14 @@ yang_parse(clicon_handle h, goto done; yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK); - - /* Step 4: Top-level augmentation of all modules */ if (yang_augment_spec(ysp) < 0) goto done; + /* sanity check of schemanode references, need more here */ + if (yang_apply((yang_node*)ysp, -1, ys_schemanode_check, NULL) < 0) + goto done; + retval = 0; done: return retval; @@ -1621,21 +1635,26 @@ yang_apply(yang_node *yn, /*! All the work for schema_nodeid functions both absolute and descendant * Ignore prefixes, see _abs - * @param[in] yn Yang node + * @param[in] yn Yang node. Find next yang stmt and return that if match. * @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b * @param[in] nvec Length of vec - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid + * @param[out] yres Result yang statement node, or NULL if not found + * @retval -1 Error, with clicon_err called + * @retval 0 OK */ -static yang_node * -schema_nodeid_vec(yang_node *yn, - char **vec, - int nvec) +static int +schema_nodeid_vec(yang_node *yn, + char **vec, + int nvec, + yang_stmt **yres) { + int retval = -1; char *arg; + yang_node *ynext; + char *nodeid; + int i; yang_stmt *ys; - yang_node *yres = NULL; - char *id; + int match; if (nvec <= 0) goto done; @@ -1644,88 +1663,121 @@ schema_nodeid_vec(yang_node *yn, __FUNCTION__, yang_key2str(yn->yn_keyword), yn->yn_argument, arg, yn->yn_len); if (strcmp(arg, "..") == 0) - ys = (yang_stmt*)yn->yn_parent; + ynext = yn->yn_parent; /* This could actually be a MODULE */ else{ /* ignore prefixes */ - if ((id = strchr(arg, ':')) == NULL) - id = arg; + if ((nodeid = strchr(arg, ':')) == NULL) + nodeid = arg; else - id++; - if ((ys = schema_nodeid_stmt(yn, id)) == NULL){ - clicon_debug(1, "%s %s not found", __FUNCTION__, id); - goto done; + nodeid++; + match = 0; + ys = NULL; + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + if (!yang_schemanode(ys)) + continue; + /* some keys dont have arguments, match on key */ + if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ + if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){ + match++; + break; + } + } else + if (ys->ys_argument && strcmp(nodeid, ys->ys_argument) == 0){ + match++; + break; + } } + if (!match){ + clicon_debug(1, "%s not found", nodeid); + goto ok; + } + ynext = (yang_node*)ys; } - if (nvec == 1){ - yres = (yang_node*)ys; + if (nvec == 1){ /* match */ + if (yang_schemanode((yang_stmt*)ynext)) + *yres = (yang_stmt*)ynext; + else + clicon_debug(1, "%s not schema node", nodeid); + goto ok; + } + /* recursive call using ynext */ + if (schema_nodeid_vec(ynext, vec+1, nvec-1, yres) < 0) goto done; - } - yres = schema_nodeid_vec((yang_node*)ys, vec+1, nvec-1); + ok: + retval = 0; done: - return yres; + return retval; } /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec - * @param[in] yn Yang node + * @param[in] yspec Yang specification. * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b * @retval NULL Error, with clicon_err called * @retval yres First yang node matching schema nodeid - * @see yang_schema_nodeid + * Assume schema nodeid:s have prefixes, (actually the first). + * @see yang_desc_schema_nodeid + * Used in yang: deviation, top-level augment */ -yang_node * -yang_abs_schema_nodeid(yang_node *yn, - char *schema_nodeid) +int +yang_abs_schema_nodeid(yang_spec *yspec, + char *schema_nodeid, + yang_stmt **yres) { + int retval = -1; char **vec = NULL; int nvec; - yang_node *yres = NULL; yang_stmt *ymod; char *id; char *prefix = NULL; + yang_stmt *yprefix; + /* check absolute schema_nodeid */ + if (schema_nodeid[0] != '/'){ + clicon_err(OE_YANG, EINVAL, "absolute schema nodeid should start with /"); + goto done; + } if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){ clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__); goto done; } - /* Assume schame nodeid looks like: "/prefix:id[/prefix:id]*" */ + /* Assume schema nodeid looks like: "/prefix:id[/prefix:id]*" */ if (nvec < 2){ clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s", __FUNCTION__, schema_nodeid); goto done; } - - /* Check for absolute path */ - if (strcmp(vec[0], "") != 0){ - clicon_err(OE_YANG, errno, "%s: %s: expected absolute path", - __FUNCTION__, vec[0]); - goto done; - } - /* Find correct module */ - if ((ymod = ys_module((yang_stmt*)yn)) == NULL){ - clicon_err(OE_YANG, 0, "Could not find top-level"); - goto done; - } /* split : */ - if ((id = strchr(vec[1], ':')) == NULL){ - id = vec[1]; + if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */ + clicon_err(OE_YANG, 0, "Absolute schema nodeid must have prefix"); + goto done; } - else{ /* other module - peek into first element to find module */ - if ((prefix = strdup(vec[1])) == NULL){ - clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); - goto done; + if ((prefix = strdup(vec[1])) == NULL){ + clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); + goto done; + } + prefix[id-vec[1]] = '\0'; + id++; + ymod = NULL; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && + strcmp(yprefix->ys_argument, prefix) == 0){ + break; } - prefix[id-vec[1]] = '\0'; - id++; - if ((ymod = yang_find_module_by_prefix((yang_stmt*)yn, prefix)) == NULL) - goto done; } - yres = schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1); + if (ymod == NULL){ + clicon_err(OE_YANG, 0, "Module with prefix %s not found", prefix); + goto done; + } + if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) + goto done; + retval = 0; done: if (vec) free(vec); if (prefix) free(prefix); - return yres; + return retval; } /*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec @@ -1734,12 +1786,14 @@ yang_abs_schema_nodeid(yang_node *yn, * @retval NULL Error, with clicon_err called * @retval yres First yang node matching schema nodeid * @see yang_schema_nodeid + * Used in yang: unique, refine, uses augment */ -yang_node * -yang_desc_schema_nodeid(yang_node *yn, - char *schema_nodeid) +int +yang_desc_schema_nodeid(yang_node *yn, + char *schema_nodeid, + yang_stmt **yres) { - yang_node *yres = NULL; + int retval = -1; char **vec = NULL; int nvec; @@ -1747,47 +1801,18 @@ yang_desc_schema_nodeid(yang_node *yn, goto done; /* check absolute schema_nodeid */ if (schema_nodeid[0] == '/'){ - clicon_err(OE_YANG, EINVAL, "descenadant schema nodeid should not start with /"); + clicon_err(OE_YANG, EINVAL, "descendant schema nodeid should not start with /"); goto done; } if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL) goto done; - yres = schema_nodeid_vec((yang_node*)yn, vec, nvec); + if (schema_nodeid_vec(yn, vec, nvec, yres) < 0) + goto done; + retval = 0; done: if (vec) free(vec); - return yres; -} - -/*! Given a schema-node-id (eg /a/b/c or a/b/c) find matching yang specification - * - * They are either absolute (start with /) or descendant (no /) - * Compare with XPATH for XML, this is for YANG spec - * @param[in] yn Yang node tree - * @param[in] schema_nodeid schema-node-id, ie a/b or /a/b - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid - * @note: the identifiers in the xpath (eg a, b in a/b) can match the nodes - * defined in yang_xpath: container, leaf,list,leaf-list, modules, sub-modules - * Example: - * yn : module m { prefix b; container b { list c { key d; leaf d; }} } - * schema-node-id = m/b/c, returns the list 'c'. - */ -yang_node * -yang_schema_nodeid(yang_node *yn, - char *schema_nodeid) -{ - yang_node *yres = NULL; - - if (strlen(schema_nodeid) == 0) - goto done; - /* check absolute schema_nodeid */ - if (schema_nodeid[0] == '/') - yres = yang_abs_schema_nodeid(yn, schema_nodeid); - else - yres = yang_desc_schema_nodeid(yn, schema_nodeid); - done: - return yres; + return retval; } /*! Parse argument as CV and save result in yang cv variable diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 79c4bea1..72a2f83c 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -1041,3 +1041,4 @@ yang_type_get(yang_stmt *ys, done: return retval; } + diff --git a/test/test_leafref.sh b/test/test_leafref.sh index e500d5b4..eea36dbc 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -27,14 +27,13 @@ module example{ } leaf address { type leafref { - path "../../interfaces/interface[name=eth0]" - + "/address/ip"; - } + path "../../interfaces/interface[name = current()/../relname]" + + "/ipv4/address/ip"; + } } } } EOF -# XXX not eth0 path "../../interface[name = current()/../ifname]" # kill old backend (if any) new "kill old backend" diff --git a/test/test_type.sh b/test/test_type.sh index 97170294..8cfb3305 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,5 +1,6 @@ #!/bin/bash # Advanced union types and generated code +# and enum w values # include err() and new() functions . ./lib.sh @@ -48,6 +49,14 @@ module example{ type af; } } + leaf status { + type enumeration { + enum up { + value 1; + } + enum down; + } + } } EOF @@ -101,6 +110,9 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/type.yang" "]]>]]>" "^]]>]]>$" +new "cli enum value" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set status down" "^$" + new "Kill backend" # Check if still alive pid=`pgrep clixon_backend` From fd91bb2933a4ed0f6be5295a830800c7f7f1df84 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 30 Jul 2017 16:16:10 +0200 Subject: [PATCH 2/3] restconf RPC --- CHANGELOG.md | 13 +-- apps/cli/cli_common.c | 2 +- apps/cli/cli_show.c | 2 +- apps/netconf/netconf_plugin.c | 38 +++----- apps/restconf/restconf_main.c | 37 +++++++- apps/restconf/restconf_methods.c | 121 ++++++++++++++++++++++++- apps/restconf/restconf_methods.h | 4 + datastore/keyvalue/clixon_keyvalue.c | 4 +- datastore/text/clixon_xmldb_text.c | 2 +- lib/clixon/clixon_xml_map.h | 13 +-- lib/clixon/clixon_yang.h | 6 +- lib/src/clixon_xml.c | 5 +- lib/src/clixon_xml_map.c | 53 +++++++---- lib/src/clixon_yang.c | 128 +++++++++++++++++++++++---- lib/src/clixon_yang_type.c | 17 ++-- test/test_netconf.sh | 3 + test/test_restconf.sh | 32 +++---- 17 files changed, 371 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b9d196..6f20b97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Upcoming 3.3.2 ### Known issues +* Please use text datastore, key-value datastore no up-to-date +* Restconf RPC does not encode output correct ### Major changes: * Added support for YANG anyxml. @@ -50,13 +52,12 @@ If you submit "nopresence" without a leaf, it will automatically be removed: ``` -* Added YANG RPC support for netconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism. - * This means you can make netconf rpc calls as defined by YANG. +* Added YANG RPC support for netconf, restconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism. + * This means you can make netconf/restconf rpc calls * However you need to register an RPC backend callback using the backend_rpc_cb_register() function. See documentation and example for more details. - * Note that RESTCONF PUT for RCP calls is not yet supported. * Example, the following YANG RPC definition enables you to run a netconf rpc. ``` - YANG: + YANG: rpc myrpc { input { leaf name { @@ -64,8 +65,10 @@ If you submit "nopresence" without a leaf, it will automatically be removed: } } } - NETCONF: + NETCONF: hello + RESTCONF: + curl -sS -X POST -d {"input":{"name":"hello"}} http://localhost/restconf/operations/myroute' ``` * Enhanced leafref functionality: diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index a507bd9c..c3a4314b 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot)) == NULL) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 15e0ce0a..8d42a328 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -165,7 +165,7 @@ expand_dbvar(void *h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index cd2f9bc7..49847e50 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -196,27 +196,6 @@ catch: return -1; } -/*! Struct to carry info into and out of ys_find_rpc callback - */ -typedef struct { - char *name; /* name of rpc */ - yang_stmt *yrpc; /* matching yang statement */ -} find_rpc_arg; - -/*! Check yang rpc statement, return yang rpc statement if found - */ -static int -ys_find_rpc(yang_stmt *ys, - void *arg) -{ - find_rpc_arg *fra = (find_rpc_arg*)arg; - - if (strcmp(fra->name, ys->ys_argument) == 0){ - fra->yrpc = ys; - return 1; /* handled */ - } - return 0; -} /*! See if there is any callback registered for this tag * @@ -240,8 +219,7 @@ netconf_plugin_callbacks(clicon_handle h, yang_stmt *yinput; yang_stmt *youtput; cxobj *xoutput; - find_rpc_arg fra = {0,0}; - int ret; + cbuf *cb; if (deps != NULL){ nreg = deps; @@ -259,13 +237,17 @@ netconf_plugin_callbacks(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* create absolute path */ + cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); /* Find yang rpc statement, return yang rpc statement if found */ - fra.name = xml_name(xn); - if ((ret = yang_apply((yang_node*)yspec, Y_RPC, ys_find_rpc, &fra)) < 0) + if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) goto done; /* Check if found */ - if (ret == 1){ - yrpc = fra.yrpc; + if (yrpc != NULL){ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -300,6 +282,8 @@ netconf_plugin_callbacks(clicon_handle h, } retval = 0; done: + if (cb) + cbuf_free(cb); return retval; } diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 0c8d519c..2f19b221 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -77,7 +77,7 @@ resource ([RFC6415]) */ #define RESTCONF_API_ROOT "/restconf/" -/*! Generic REST method, GET, PUT, DELETE +/*! Generic REST method, GET, PUT, DELETE, etc * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) @@ -120,6 +120,38 @@ api_data(clicon_handle h, return retval; } +/*! Operations REST method, POST + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] dvec Stream input data + */ +static int +api_operations(clicon_handle h, + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + int retval = -1; + char *request_method; + + clicon_debug(1, "%s", __FUNCTION__); + request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); + clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); + if (strcmp(request_method, "POST")==0) + retval = api_operation_post(h, r, path, pcvec, pi, qvec, data); + else + retval = notfound(r); + return retval; +} + + /*! Process a FastCGI request * @param[in] r Fastcgi request handle */ @@ -176,6 +208,9 @@ request_process(clicon_handle h, if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ retval = api_data(h, r, path, pcvec, 2, qvec, data); + else + if (strcmp(method, "operations") == 0) /* rpc */ + retval = api_operations(h, r, path, pcvec, 2, qvec, data); else if (strcmp(method, "test") == 0) retval = test(r, 0); else diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 9e2f4db5..35299ebf 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -354,7 +354,7 @@ api_data_post(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json into xml */ if (json_parse_str(data, &xdata) < 0){ @@ -462,7 +462,7 @@ api_data_put(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json into xml */ if (json_parse_str(data, &xdata) < 0){ @@ -581,7 +581,7 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot)) == NULL) goto done; @@ -606,7 +606,7 @@ api_data_delete(clicon_handle h, FCGX_FPrintF(r->out, "\r\n"); retval = 0; done: - if (cbx) + if (cbx) cbuf_free(cbx); if (xtop) xml_free(xtop); @@ -614,3 +614,116 @@ api_data_delete(clicon_handle h, return retval; } +/*! REST operation POST method + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @note We map post to edit-config create. + + POST {+restconf}/operations/ + + + */ +int +api_operation_post(clicon_handle h, + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) +{ + int retval = -1; + int i; + char *oppath = path; + yang_stmt *yrpc = NULL; + yang_spec *yspec; + yang_stmt *yinput; + cxobj *xdata = NULL; + cxobj *xret = NULL; + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* xpath root */ + cxobj *xbot = NULL; + yang_node *y = NULL; + cxobj *xinput; + cxobj *x; + cxobj **vec = NULL; + + clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + for (i=0; i... + * eg + */ + /* Create config top-of-tree */ + if ((xtop = xml_new("rpc", NULL)) == NULL) + goto done; + xbot = xtop; + if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) + goto done; + if (data){ + /* Parse input data as json into xml */ + if (json_parse_str(data, &xdata) < 0){ + clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); + goto done; + } + /* xdata should have format */ + if ((xinput = xpath_first(xdata, "/input")) != NULL){ + /* Add all input under path */ + x = NULL; + while ((x = xml_child_each(xinput, x, -1)) != NULL) + if (xml_addsub(xbot, x) < 0) + goto done; + if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ + xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */ + if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0) + goto done; + if (xml_apply(xinput, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + goto done; + if (xml_yang_validate_add(xinput, NULL) < 0) + goto done; + } + } + } + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((cbx = cbuf_new()) == NULL) + goto done; + /* Sanity check of outgoing XML */ + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + vec = xml_childvec_get(xret); + if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) + goto done; + clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xdata) + xml_free(xdata); + if (xret) + xml_free(xret); + if (cbx) + cbuf_free(cbx); + return retval; +} diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index f6946f8a..661e01ba 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -60,4 +60,8 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *qvec, char *data); int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi); +int api_operation_post(clicon_handle h, FCGX_Request *r, + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data); + #endif /* _RESTCONF_METHODS_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 3fb090c0..0c1ff566 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -321,7 +321,7 @@ get(char *dbname, restval++; } if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name)) == NULL){ + if ((y = yang_find_topnode(ys, name, 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -808,7 +808,7 @@ kv_put(xmldb_handle xh, } // clicon_log(LOG_WARNING, "%s", __FUNCTION__); while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x), 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); goto done; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 386be2c9..d255d69f 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -663,7 +663,7 @@ text_modify_top(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index bfd27302..1cff0413 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -38,16 +38,6 @@ #ifndef _CLIXON_XML_MAP_H_ #define _CLIXON_XML_MAP_H_ -/* - * lvmap_xml op codes - */ -enum { - LVXML, /* a.b{x=1} -> 1 */ - LVXML_VAL, /* a.b{x=1} -> 1 */ - LVXML_VECVAL, /* key: a.b.0{x=1} -> 1 och */ - LVXML_VECVAL2, /* key: a.b.0{x=1} -> 1 och */ -}; - /* * Prototypes */ @@ -73,7 +63,8 @@ int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); -int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp); +int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, + int schemanode, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 9593181c..d62cfabe 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -133,6 +133,7 @@ enum rfc_6020{ typedef struct yang_stmt yang_stmt; /* forward */ /*! Yang type cache. Yang type statements can cache all typedef info here + * @note unions not cached */ struct yang_type_cache{ int yc_options; @@ -158,7 +159,7 @@ struct yang_stmt{ leaf, leaf-list, mandatory, fraction-digits */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max */ - yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data */ + yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ }; @@ -205,7 +206,8 @@ yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name); +yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); +yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 355e2834..6416e342 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -974,7 +974,10 @@ clicon_xml2cbuf(cbuf *cb, } if (prettyprint && xml_body(x)==NULL) cprintf(cb, "%*s", level*XML_INDENT, ""); - cprintf(cb, "", name); + cprintf(cb, "", name); } if (prettyprint) cprintf(cb, "\n"); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 449adc23..007c68db 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -644,7 +644,7 @@ xml_diff1(yang_stmt *ys, while ((x1 = xml_child_each(xt1, x1, CX_ELMNT)) != NULL){ name = xml_name(x1); if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name); + y = yang_find_topnode((yang_spec*)ys, name, 0); else y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ @@ -754,7 +754,7 @@ xml_diff1(yang_stmt *ys, while ((x2 = xml_child_each(xt2, x2, CX_ELMNT)) != NULL){ name = xml_name(x2); if (ys->ys_keyword == Y_SPEC) - y = yang_find_topnode((yang_spec*)ys, name); + y = yang_find_topnode((yang_spec*)ys, name, 0); else y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ @@ -1482,7 +1482,7 @@ xml_spec_populate(cxobj *x, (yp = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yp, xml_name(x)); else - y = yang_find_topnode(yspec, name); /* still NULL for config */ + y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ #ifdef XXX_OBSOLETE /* Add validate elsewhere */ if (y==NULL){ clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", @@ -1544,7 +1544,7 @@ api_path2xpath_cvv(yang_spec *yspec, clicon_debug(1, "[%d] cvname:%s", i, name); clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0)); if (i == offset){ - if ((y = yang_find_topnode(yspec, name)) == NULL){ + if ((y = yang_find_topnode(yspec, name, 0)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1635,17 +1635,19 @@ api_path2xpath(yang_spec *yspec, * @param[in] nvec Length of vec * @param[in] x0 Xpath tree so far * @param[in] y0 Yang spec for x0 + * @param[in] schemanode If set use schema nodes otherwise data nodes. * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml */ static int -api_path2xml_vec(char **vec, - int nvec, - cxobj *x0, - yang_node *y0, - cxobj **xpathp, - yang_node **ypathp) +api_path2xml_vec(char **vec, + int nvec, + cxobj *x0, + yang_node *y0, + int schemanode, + cxobj **xpathp, + yang_node **ypathp) { int retval = -1; int j; @@ -1663,6 +1665,7 @@ api_path2xml_vec(char **vec, int nvalvec; cxobj *x = NULL; yang_stmt *y = NULL; + char *local; if ((name = vec[0]) == NULL){ if (xpathp) @@ -1678,10 +1681,21 @@ api_path2xml_vec(char **vec, if (percent_decode(restval_enc, &restval) < 0) goto done; } - if (y0->yn_keyword == Y_SPEC) /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name); - else - y = yang_find_datanode((yang_node*)y0, name); + /* Split into prefix and localname, ignore prefix for now */ + if ((local = index(name, ':')) != NULL){ + *local = '\0'; + local++; + name = local; + } + if (y0->yn_keyword == Y_SPEC){ /* top-node */ + clicon_debug(1, "%s 1 %s", __FUNCTION__, name); + y = yang_find_topnode((yang_spec*)y0, name, schemanode); + } + else { + clicon_debug(1, "%s 2 %s", __FUNCTION__, name); + y = schemanode?yang_find_schemanode((yang_node*)y0, name): + yang_find_datanode((yang_node*)y0, name); + } if (y == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", name); goto done; @@ -1756,6 +1770,7 @@ api_path2xml_vec(char **vec, } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, + schemanode, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1769,14 +1784,17 @@ api_path2xml_vec(char **vec, /*! Create xml tree from api-path * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] yspec Yang spec + * @param[in] schemanode If set use schema nodes otherwise data nodes. * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml_vec */ int -api_path2xml(char *api_path, +api_path2xml(char *api_path, yang_spec *yspec, cxobj *xpath, + int schemanode, cxobj **xpathp, yang_node **ypathp) { @@ -1800,7 +1818,7 @@ api_path2xml(char *api_path, } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, - xpath, (yang_node*)yspec, + xpath, (yang_node*)yspec, schemanode, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1984,7 +2002,7 @@ xml_merge(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } @@ -2050,3 +2068,4 @@ yang_enum_int_value(cxobj *node, done: return retval; } + diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 5e490e08..676b1c42 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -453,18 +453,68 @@ yang_find_datanode(yang_node *yn, return ysmatch; } -/*! Find 'top-node', eg first data node in all (sub)modules in a yang spec +/*! Find child schema node with matching argument (container, leaf, etc) + * @note XXX unify code with yang_find_datanode? + * @see yang_find_datanode + */ +yang_stmt * +yang_find_schemanode(yang_node *yn, + char *argument) +{ + yang_stmt *ys = NULL; + yang_stmt *yc = NULL; + yang_stmt *ysmatch = NULL; + int i, j; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ + for (j=0; jys_len; j++){ + yc = ys->ys_stmt[j]; + if (yc->ys_keyword == Y_CASE) /* Look for its children */ + ysmatch = yang_find_schemanode((yang_node*)yc, argument); + else + if (yang_schemanode(yc)){ + if (argument == NULL) + ysmatch = yc; + else + if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) + ysmatch = yc; + } + if (ysmatch) + goto match; + } + } /* Y_CHOICE */ + else + if (yang_schemanode(ys)){ + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + } + } + match: + return ysmatch; +} + + +/*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] schemanode If set look for schema nodes, otherwise only data nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to - * look for syntax-nodes. Note that if a child to a module is a choice, + * look for nodes. Note that if a child to a module is a choice, * the search is made recursively made to the choice's children. */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *name) + char *name, + int schemanode) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -472,12 +522,43 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) - return yc; + if (schemanode){ + if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) + return yc; + } + else + if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) + return yc; } return NULL; } +/*! Given a yang statement, find the prefix associated to this module + * @param[in] ys Yang statement + * @retval NULL Not found + * @retval prefix Prefix as char* pointer into yang tree + */ +char * +yang_find_myprefix(yang_stmt *ys) +{ + yang_stmt *ymod; /* My module */ + yang_stmt *yprefix; + char *prefix = NULL; + + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang module not found"); + goto done; + } + if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) == NULL){ + clicon_err(OE_YANG, 0, "No prefix in my module"); + goto done; + } + prefix = yprefix->ys_argument; + done: + return prefix; +} + + /*! Reset flag in complete tree, arg contains flag */ static int ys_flag_reset(yang_stmt *ys, @@ -505,8 +586,13 @@ ys_module(yang_stmt *ys) { yang_node *yn; +#if 1 + if (ys==NULL || ys->ys_keyword==Y_SPEC) + return NULL; +#else if (ys==NULL || ys->ys_keyword==Y_SPEC) return ys; +#endif while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){ yn = ys->ys_parent; /* Some extra stuff to ensure ys is a stmt */ @@ -572,6 +658,7 @@ ytype_prefix(yang_stmt *ys) } + /*! Given a yang statement and a prefix, return yang module to that prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement @@ -588,22 +675,25 @@ yang_find_module_by_prefix(yang_stmt *ys, yang_stmt *my_ymod; yang_stmt *ymod = NULL; yang_spec *yspec; + char *myprefix; + if ((yspec = ys_spec(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang spec not found"); + goto done; + } + myprefix = yang_find_myprefix(ys); if ((my_ymod = ys_module(ys)) == NULL){ clicon_err(OE_YANG, 0, "My yang module not found"); goto done; } - if ((yspec = ys_spec(my_ymod)) == NULL){ - clicon_err(OE_YANG, 0, "My yang spec not found"); - goto done; - } +#if 0 if (my_ymod->ys_keyword != Y_MODULE && my_ymod->ys_keyword != Y_SUBMODULE){ clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument); goto done; } - if ((yprefix = yang_find((yang_node*)my_ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) == 0){ +#endif + if (strcmp(myprefix, prefix) == 0){ ymod = my_ymod; goto done; } @@ -1730,6 +1820,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, yang_stmt *ymod; char *id; char *prefix = NULL; + yang_stmt *yprefix; /* check absolute schema_nodeid */ @@ -1765,9 +1856,16 @@ yang_abs_schema_nodeid(yang_spec *yspec, break; } } - if (ymod == NULL){ - clicon_err(OE_YANG, 0, "Module with prefix %s not found", prefix); - goto done; + if (ymod == NULL){ /* Try with topnode */ + yang_stmt *ys; + if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ + clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); + goto done; + } + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); + goto done; + } } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) goto done; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 72a2f83c..37393909 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -107,6 +107,8 @@ yang_builtin(char *type) return 0; } +/*! Set type cache for yang type + */ int yang_type_cache_set(yang_type_cache **ycache0, yang_stmt *resolved, @@ -142,7 +144,6 @@ yang_type_cache_set(yang_type_cache **ycache0, } ycache->yc_fraction = fraction; retval = 0; - done: return retval; } @@ -209,6 +210,7 @@ yang_type_cache_free(yang_type_cache *ycache) * @param[in] ys This is a type statement * @param[in] arg Not used * Typically only called once when loading te yang type system. + * @note unions not cached */ int ys_resolve_type(yang_stmt *ys, @@ -226,13 +228,16 @@ ys_resolve_type(yang_stmt *ys, if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, &options, &mincv, &maxcv, &pattern, &fraction) < 0) goto done; - /* skip unions since they may have different sets of options, mincv, etc */ + if (resolved && strcmp(resolved->ys_argument, "union")==0) - ; + ; + /* skip unions since they may have different sets of options, mincv, etc + * You would have to resolve all sub-types also recursively + */ else - if (yang_type_cache_set(&ys->ys_typecache, - resolved, options, mincv, maxcv, pattern, fraction) < 0) - goto done; + if (yang_type_cache_set(&ys->ys_typecache, + resolved, options, mincv, maxcv, pattern, fraction) < 0) + goto done; retval = 0; done: return retval; diff --git a/test/test_netconf.sh b/test/test_netconf.sh index c1826ab8..39fd6772 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -126,6 +126,9 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "netconf rpc" +expecteof "$clixon_netconf -qf $clixon_cf" "ipv4ipv4]]>]]>" "^ipv4" + +new "netconf rpc w/o namespace" expecteof "$clixon_netconf -qf $clixon_cf" "ipv4ipv4]]>]]>" "^ipv4" new "netconf subscription" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index cd4df191..b9e9e236 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -35,53 +35,55 @@ expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application new "restconf get empty config" expectfn "curl -sSG http://localhost/restconf/data" "^null $" -# -new "Add subtree to datastore using POST" +new "restconf Add subtree to datastore using POST" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" -new "Check interfaces eth/0/0 added" +new "restconf Check interfaces eth/0/0 added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' -new "delete interfaces" +new "restconf delete interfaces" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' "" -new "Check empty config" +new "restconf Check empty config" expectfn "curl -sSG http://localhost/restconf/data" "^null $" -new "Add interfaces subtree eth/0/0 using POST" +new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" -new "Check eth/0/0 added" +new "restconf Check eth/0/0 added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' -new "Re-post eth/0/0 which should generate error" +new "restconf Re-post eth/0/0 which should generate error" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists" new "Add leaf description using POST" expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" -new "Check description added" +new "restconf Check description added" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}} $' -new "delete eth/0/0" +new "restconf delete eth/0/0" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" -#new "Check deleted eth/0/0" -#expectfn 'curl -sS -G http://localhost/restconf/data' '{"interfaces": null} -#$' +new "Check deleted eth/0/0" +expectfn 'curl -sS -G http://localhost/restconf/data' "^null $" -new "Re-Delete eth/0/0 using none should generate error" +new "restconf Re-Delete eth/0/0 using none should generate error" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" -new "Add subtree eth/0/0 using PUT" +new "restconf Add subtree eth/0/0 using PUT" expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +new "restconf get subtree" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' +new "restconf rpc using POST (wrong output encoding)" +expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"rpc-reply": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} $' + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf From 265ed859def216046b139b50d91ae346774fd9f5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 1 Aug 2017 19:39:19 +0200 Subject: [PATCH 3/3] mem leak --- README.md | 11 ++- apps/netconf/netconf_plugin.c | 2 +- apps/restconf/restconf_lib.c | 14 +++- apps/restconf/restconf_main.c | 5 +- apps/restconf/restconf_methods.c | 118 +++++++++++++++++++++++++------ lib/src/clixon_json.c | 4 +- test/lib.sh | 2 +- test/test_restconf.sh | 20 ++++-- 8 files changed, 137 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index e15be22b..2b1f5094 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,14 @@ used to generate an interactive CLI client as well as provide [Restconf](apps/restconf/README.md) clients. The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions: -- object-references -- if-feature -- unique +- conformance: feature, if-feature, deviation +- identy, base, identityref +- list features: min/max-elements, unique, ordered-by + +There are also new features in YANG 1.1 [YANG RFC +7950](https://www.rfc-editor.org/rfc/rfc7950.txt) most of which are +not implemented. + diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index 49847e50..d5b3f427 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -269,7 +269,7 @@ netconf_plugin_callbacks(clicon_handle h, if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yinput) < 0) + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) goto done; if (xml_apply(xoutput, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 1f0073f7..80571eac 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -264,7 +264,19 @@ test(FCGX_Request *r, printparam(r, "SERVER_NAME", dbg); printparam(r, "HTTP_COOKIE", dbg); printparam(r, "HTTPS", dbg); - + printparam(r, "HTTP_ACCEPT", dbg); + printparam(r, "HTTP_CONTENT_TYPE", dbg); +#if 0 /* For debug */ + clicon_debug(1, "All environment vars:"); + { + extern char **environ; + int i; + for (i = 0; environ[i] != NULL; i++){ + clicon_debug(1, "%s", environ[i]); + } + } + clicon_debug(1, "End environment vars:"); +#endif return 0; } diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 2f19b221..97cf3f36 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -151,7 +151,6 @@ api_operations(clicon_handle h, return retval; } - /*! Process a FastCGI request * @param[in] r Fastcgi request handle */ @@ -189,12 +188,10 @@ request_process(clicon_handle h, clicon_debug(1, "DATA=%s", data); if (str2cvec(data, '&', '=', &dvec) < 0) goto done; - if ((method = pvec[2]) == NULL){ retval = notfound(r); goto done; } - retval = 0; test(r, 1); /* If present, check credentials */ @@ -392,7 +389,7 @@ main(int argc, if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){ if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 || strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0) - request_process(h, r); + request_process(h, r); /* This is the function */ else{ clicon_debug(1, "top-level not found"); notfound(r); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 35299ebf..e2d7412f 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -161,8 +161,13 @@ api_data_get_gen(clicon_handle h, cbuf *cbj = NULL;; int code; const char *reason_phrase; + char *media_accept; + int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s", __FUNCTION__); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; yspec = clicon_dbspec_yang(h); if ((path = cbuf_new()) == NULL) goto done; @@ -213,15 +218,22 @@ api_data_get_gen(clicon_handle h, if ((cbx = cbuf_new()) == NULL) goto done; FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); if (head) goto ok; clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret)); - vec = xml_childvec_get(xret); + clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret)); - if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) - goto done; + if (use_xml){ + if (clicon_xml2cbuf(cbx, xret, 0, 1) < 0) /* Dont print top object? */ + goto done; + } + else{ + vec = xml_childvec_get(xret); + if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) + goto done; + } clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); @@ -276,8 +288,8 @@ api_data_head(clicon_handle h, * Request may contain * Accept: application/yang.data+json,application/yang.data+xml * Response contains one of: - * Content-Type: application/yang.data+xml - * Content-Type: application/yang.data+json + * Content-Type: application/yang-data+xml + * Content-Type: application/yang-data+json * NOTE: If a retrieval request for a data resource representing a YANG leaf- * list or list object identifies more than one instance, and XML * encoding is used in the response, then an error response containing a @@ -340,10 +352,15 @@ api_data_post(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; + char *media_content_type; + int parse_xml = 0; /* By default expect and parse JSON */ clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -356,8 +373,14 @@ api_data_post(clicon_handle h, xbot = xtop; if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; - /* Parse input data as json into xml */ - if (json_parse_str(data, &xdata) < 0){ + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (clicon_xml_parse_str(data, &xdata) < 0){ + clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); + goto done; + } + } + else if (json_parse_str(data, &xdata) < 0){ clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); goto done; } @@ -448,10 +471,15 @@ api_data_put(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; + char *media_content_type; + int parse_xml = 0; /* By default expect and parse JSON */ clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -464,8 +492,14 @@ api_data_put(clicon_handle h, xbot = xtop; if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; - /* Parse input data as json into xml */ - if (json_parse_str(data, &xdata) < 0){ + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (clicon_xml_parse_str(data, &xdata) < 0){ + clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); + goto done; + } + } + else if (json_parse_str(data, &xdata) < 0){ clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); goto done; } @@ -642,6 +676,7 @@ api_operation_post(clicon_handle h, yang_stmt *yrpc = NULL; yang_spec *yspec; yang_stmt *yinput; + yang_stmt *youtput; cxobj *xdata = NULL; cxobj *xret = NULL; cbuf *cbx = NULL; @@ -649,10 +684,23 @@ api_operation_post(clicon_handle h, cxobj *xbot = NULL; yang_node *y = NULL; cxobj *xinput; + cxobj *xoutput; cxobj *x; - cxobj **vec = NULL; + char *media_content_type; + int parse_xml = 0; /* By default expect and parse JSON */ + char *media_accept; + int use_xml = 0; /* By default return JSON */ clicon_debug(1, "%s json:\"%s\"", __FUNCTION__, data); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; + clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"", + __FUNCTION__, media_accept, media_content_type); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -677,8 +725,14 @@ api_operation_post(clicon_handle h, if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) goto done; if (data){ - /* Parse input data as json into xml */ - if (json_parse_str(data, &xdata) < 0){ + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (clicon_xml_parse_str(data, &xdata) < 0){ + clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); + goto done; + } + } + else if (json_parse_str(data, &xdata) < 0){ clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data); goto done; } @@ -705,22 +759,44 @@ api_operation_post(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; + xoutput=xpath_first(xret, "/"); + if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL && + xoutput){ + xml_name_set(xoutput, "output"); + clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); + cbuf_reset(cbx); + xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) + goto done; + if (xml_apply(xoutput, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + goto done; + if (xml_yang_validate_add(xoutput, NULL) < 0) + goto done; + } /* Sanity check of outgoing XML */ FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n"); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); - vec = xml_childvec_get(xret); - if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0) - goto done; - clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); - + if (xoutput){ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xoutput, 0, 1) < 0) + goto done; + } + else + if (xml2json_cbuf(cbx, xoutput, 1) < 0) + goto done; + clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + } retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xdata) xml_free(xdata); + if (xtop) + xml_free(xtop); if (xret) xml_free(xret); if (cbx) diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 8d8168cd..c2c0934a 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -412,7 +412,7 @@ xml2json1_cbuf(cbuf *cb, * goto err; * cbuf_free(cb); * @endcode - * See also xml2json + * @see clicon_xml2cbuf */ int xml2json_cbuf(cbuf *cb, @@ -442,7 +442,7 @@ xml2json_cbuf(cbuf *cb, /*! Translate a vector of xml objects to JSON CLigen buffer. * This is done by adding a top pseudo-object, and add the vector as subs, - * and then not pritning the top pseudo-.object using the 'flat' option. + * and then not printing the top pseudo-object using the 'flat' option. * @param[out] cb Cligen buffer to write to * @param[in] vec Vector of xml objecst * @param[in] veclen Length of vector diff --git a/test/lib.sh b/test/lib.sh index 88c60af6..7194396d 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -40,7 +40,7 @@ expectfn(){ return fi # grep extended grep - match=`echo "$ret" | grep -EZo "$expect"` + match=`echo $ret | grep -EZo "$expect"` # echo "ret:\"$ret\"" # echo "expect:\"$expect\"" # echo "match:\"$match\"" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index b9e9e236..9bad8f9f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -30,10 +30,11 @@ new "restconf options" expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" new "restconf head" -expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json" +expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK" +#Content-Type: application/yang-data+json" new "restconf get empty config" -expectfn "curl -sSG http://localhost/restconf/data" "^null $" +expectfn "curl -sSG http://localhost/restconf/data" "null" new "restconf Add subtree to datastore using POST" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "" @@ -46,7 +47,7 @@ new "restconf delete interfaces" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' "" new "restconf Check empty config" -expectfn "curl -sSG http://localhost/restconf/data" "^null $" +expectfn "curl -sSG http://localhost/restconf/data" "null" new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "" @@ -69,7 +70,7 @@ new "restconf delete eth/0/0" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "Check deleted eth/0/0" -expectfn 'curl -sS -G http://localhost/restconf/data' "^null $" +expectfn 'curl -sS -G http://localhost/restconf/data' "null" new "restconf Re-Delete eth/0/0 using none should generate error" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" @@ -81,8 +82,15 @@ new "restconf get subtree" expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}} $' -new "restconf rpc using POST (wrong output encoding)" -expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"rpc-reply": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} $' +new "restconf rpc using POST json" +expectfn 'curl -sS -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{ "output": { "route": { "address-family": "ipv4", "next-hop": { "next-hop-list": "2.3.4.5" } } } } ' + +new "restconf rpc using POST xml" +ret=$(curl -sS -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) +expect=" ipv4 2.3.4.5 " +match=`echo $ret | grep -EZo "$expect"` +echo -n "ret: " +echo $ret new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf