diff --git a/CHANGELOG.md b/CHANGELOG.md index a995b13f..83a75a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Clixon CHANGELOG + +- Added validation for leafref forward and nackward references. - Added new backend plugin callback: "plugin_statedata()" for retreiving state data diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index ec0b0887..8eb4c03b 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -89,15 +89,19 @@ generic_validate(yang_spec *yspec, int retval = -1; cxobj *x1; cxobj *x2; - int i; yang_stmt *ys; + int i; + + /* All entries */ + if (xml_apply(td->td_target, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + goto done; /* changed entries */ for (i=0; itd_clen; i++){ x1 = td->td_scvec[i]; /* source changed */ x2 = td->td_tcvec[i]; /* target changed */ - ys = xml_spec(x1); - if (xml_yang_validate(x2, ys) < 0) + if (xml_yang_validate_add(x2, NULL) < 0) goto done; } /* deleted entries */ @@ -113,11 +117,8 @@ generic_validate(yang_spec *yspec, /* added entries */ for (i=0; itd_alen; i++){ x2 = td->td_avec[i]; - ys = xml_spec(x2); - if (xml_yang_validate(x2, ys) < 0) - goto done; - if (xml_apply(x2, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate, NULL) < 0) + if (xml_apply0(x2, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_add, NULL) < 0) goto done; } retval = 0; diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index acbfa3bf..5fb4cfa6 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -275,9 +275,10 @@ yang2cli_var_sub(clicon_handle h, if (helptext) cprintf(cb0, "(\"%s\")", helptext); if (completion){ +#if 0 if (type && (strcmp(type, "leafref") == 0)){ yang_stmt *ypath; - + /* XXX only for absolute xpath */ if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ clicon_err(OE_XML, 0, "leafref should have path sub"); goto done; @@ -290,6 +291,7 @@ yang2cli_var_sub(clicon_handle h, ypath->ys_argument); } else +#endif if (cli_expand_var_generate(h, ys, cvtype, cb0, options, fraction_digits) < 0) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 6240ac31..05489770 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -141,12 +141,29 @@ expand_dbvar(void *h, if (api_path_fmt2xpath(api_path, cvv, &xpath) < 0) goto done; /* XXX read whole configuration, why not send xpath? */ - if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0) + if (clicon_rpc_get_config(h, dbstr, xpath, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error(xerr); goto done; } +#if 0 + /* Get xpath from datastore? + * 1. Get whole datastore, + * 2. Add tentative my location to xpath, + * 3. If leafref, compute relative xpath + */ + { + cxobj *xcur = NULL; /* xpath, NULL if datastore */ + // yang_node *y = NULL; /* yang spec of xpath */ + + if ((xcur = xpath_first(xt, xpath)) == NULL) + goto done; + + + } +#endif + /* One round to detect duplicates * XXX The code below would benefit from some cleanup */ diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index 9a2e611f..474da891 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -196,6 +196,26 @@ catch: return -1; } +static int +ys_find_rpc(yang_stmt *ys, + void *arg) +{ + cxobj *xn = (cxobj*)arg; + char *name = xml_name(xn); + + if (ys->ys_keyword == Y_RPC && strcmp(name, ys->ys_argument) == 0){ + /* + * XXX + * 1. Check xn arguments with input statement. + * 2. Send to backend as clicon_msg-encode() + * 3. In backend to similar but there call actual backend + */ + return 1; /* handled */ + } + return 0; +} + + /*! See if there is any callback registered for this tag * * @param[in] h clicon handle @@ -211,21 +231,28 @@ netconf_plugin_callbacks(clicon_handle h, cxobj *xn, cxobj **xret) { + int retval = -1; netconf_reg_t *nreg; - int retval; + yang_spec *yspec; - if (deps == NULL) - return 0; - nreg = deps; - do { - if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){ - if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0) - return -1; - else - return 1; /* handled */ - } - nreg = NEXTQ(netconf_reg_t *, nreg); - } while (nreg != deps); - return 0; + if (deps != NULL){ + nreg = deps; + do { + if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){ + retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg); + goto done; + } + nreg = NEXTQ(netconf_reg_t *, nreg); + } while (nreg != deps); + } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (yang_apply((yang_node*)yspec, ys_find_rpc, xn) < 0) + goto done; + retval = 0; + done: + return retval; } diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index aba551bf..36cfda82 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -53,7 +53,8 @@ enum { */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); -int xml_yang_validate(cxobj *xt, yang_stmt *ys) ; +int xml_yang_validate_add(cxobj *xt, void *arg); +int xml_yang_validate_all(cxobj *xt, void *arg); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 3e2ebc45..2639f6df 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -274,16 +274,62 @@ xml2cli(FILE *f, return retval; } +/*! Validate an xml node of type leafref, ensure the value is one of that path's reference + * @param[in] xt XML leaf node of type leafref + * @param[in] ytype Yang type statement belonging to the XML node + */ +static int +validate_leafref(cxobj *xt, + yang_stmt *ytype) +{ + int retval = -1; + yang_stmt *ypath; + cxobj **xvec = NULL; + cxobj *x; + int i; + size_t xlen = 0; + char *leafrefbody; + char *leafbody; -/*! Validate a single XML node with yang specification - * - If no value and mandatory flag set in spec, report error. - * - Validate value versus spec, and report error if no match. Currently - * only int ranges and string regexp checked. - * @retval 0 OK - */ + + if ((leafrefbody = xml_body(xt)) == NULL) + return 0; + if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ + clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); + goto done; + } + if (xpath_vec(xt, ypath->ys_argument, &xvec, &xlen) < 0) + goto done; + for (i = 0; i < xlen; i++) { + x = xvec[i]; + if ((leafbody = xml_body(x)) == NULL) + continue; + if (strcmp(leafbody, leafrefbody) == 0) + break; + } + if (i==xlen){ + clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s", + leafrefbody); + goto done; + } + retval = 0; + done: + if (xvec) + free(xvec); + return retval; +} + +/*! Validate a single XML node with yang specification for added entry + * 1. Check if mandatory leafs present as subs. + * 2. Check leaf values, eg int ranges and string regexps. + * @param[in] xt XML node to be validated + * @retval 0 Valid OK + * @retval -1 Validation failed + * @see xml_yang_validate_all + */ int -xml_yang_validate(cxobj *xt, - yang_stmt *ys0) +xml_yang_validate_add(cxobj *xt, + void *arg) { int retval = -1; cg_var *cv = NULL; @@ -294,7 +340,7 @@ xml_yang_validate(cxobj *xt, char *body; /* if not given by argument (overide) use default link */ - ys = ys0?ys0:xml_spec(xt); + ys = xml_spec(xt); switch (ys->ys_keyword){ case Y_LIST: /* fall thru */ @@ -346,6 +392,43 @@ xml_yang_validate(cxobj *xt, return retval; } +/*! Validate a single XML node with yang specification for all (not only added) entries + * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. + * @param[in] xt XML node to be validated + * @retval 0 Valid OK + * @retval -1 Validation failed + * @see xml_yang_validate_add + */ +int +xml_yang_validate_all(cxobj *xt, + void *arg) +{ + int retval = -1; + yang_stmt *ys; + yang_stmt *ytype; + + /* if not given by argument (overide) use default link */ + ys = xml_spec(xt); + switch (ys->ys_keyword){ + case Y_LEAF: + /* fall thru */ + case Y_LEAF_LIST: + /* Special case if leaf is leafref, then first check against + current xml tree + */ + if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL && + strcmp(ytype->ys_argument, "leafref") == 0) + if (validate_leafref(xt, ytype) < 0) + goto done; + break; + default: + break; + } + retval = 0; + done: + return retval; +} + /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt @@ -1908,3 +1991,4 @@ xml_merge(cxobj *x0, done: return retval; } + diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index 5928a4c6..5b953c04 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -435,6 +435,7 @@ recursive_find(cxobj *xn, * - @= * - * - = # RelationalExpr '=' RelationalExpr + * - =current() XXX * @see https://www.w3.org/TR/xpath/#predicates */ static int @@ -565,6 +566,7 @@ xpath_find(struct xpath_element *xe, cxobj *xv; int descendants = 0; cxobj **vec1 = NULL; + cxobj *xparent; size_t vec1len = 0; struct xpath_predicate *xp; @@ -587,10 +589,13 @@ xpath_find(struct xpath_element *xe, case A_SELF: break; case A_PARENT: + j = 0; for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; if (fn(ys, arg) < 0) goto done; - if (yang_apply((yang_node*)ys, fn, arg) < 0) + if ((ret = yang_apply((yang_node*)ys, fn, arg)) < 0) goto done; + if (ret > 0){ + retval = ret; + goto done; + } } retval = 0; done: diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 8fc59ed0..99100149 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -90,7 +90,6 @@ static const map_str2int ytmap[] = { {"int16", CGV_INT16}, {"int64", CGV_INT64}, {"leafref", CGV_STRING}, /* XXX */ - {"uint8", CGV_UINT8}, {"uint16", CGV_UINT16}, {"uint32", CGV_UINT32}, @@ -105,12 +104,6 @@ yang_builtin(char *type) { if (clicon_str2int(ytmap, type) != -1) return 1; -#if 0 - const struct map_str2int *yt; - for (yt = &ytmap[0]; yt->ms_str; yt++) - if (strcmp(yt->ms_str, type) == 0) - return 1; -#endif return 0; } @@ -857,6 +850,7 @@ yang_type_resolve(yang_stmt *ys, ylength = yang_find((yang_node*)ytype, Y_LENGTH, NULL); ypattern = yang_find((yang_node*)ytype, Y_PATTERN, NULL); yfraction = yang_find((yang_node*)ytype, Y_FRACTION_DIGITS, NULL); + /* Check if type is basic type. If so, return that */ if (prefix == NULL && yang_builtin(type)){ *yrestype = ytype; @@ -867,8 +861,10 @@ yang_type_resolve(yang_stmt *ys, /* Not basic type. Now check if prefix which means we look in other module */ if (prefix){ /* Go to top and find import that matches */ - if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) + if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){ + clicon_err(OE_DB, 0, "Module not resolved: %s", prefix); goto done; + } if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL) goto ok; /* unresolved */ } diff --git a/test/test7.sh b/test/test7.sh new file mode 100755 index 00000000..e7555e4a --- /dev/null +++ b/test/test7.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Test7: Yang specifics: leafref + +# include err() and new() functions +. ./lib.sh + +# For memcheck +# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" +clixon_netconf=clixon_netconf +clixon_cli=clixon_cli + +cat < /tmp/leafref.yang +module ietf-ip{ + typedef admin-status{ + type string; + } + list interface { + key "name"; + leaf name { + type string; + } + leaf admin-status { + type admin-status; + } + list address { + key "ip"; + leaf ip { + type string; + } + } + } + container default-address { + leaf ifname { + type leafref { + path "../../interface/name"; + } + } + leaf address { + type leafref { + path "../../interface[name = current()/../ifname]" + + "/address/ip"; + } + } + } +} +EOF + + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $clixon_cf -y /tmp/leafref +if [ $? -ne 0 ]; then + err +fi + +new "start backend" +# start new backend +sudo clixon_backend -If $clixon_cf -y /tmp/leafref +if [ $? -ne 0 ]; then + err +fi + +new "leafref base config" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0up
192.0.2.1
192.0.2.2
loup
127.0.0.1
]]>]]>" "^]]>]]>$" + +new "leafref get config" +expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^eth0' + +new "leafref base commit" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^]]>]]>$" + +new "leafref add wrong ref" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" + +new "leafref validate" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^missing-attribute" + +new "leafref discard-changes" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + +new "leafref add correct ref" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0
192.0.2.2
]]>]]>" "^]]>]]>$" + +new "leafref validate (ok)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^" + +new "leafref delete leaf" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0]]>]]>" "^" + +new "leafref validate (should fail)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^missing-attribute" + + +new "Kill backend" +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $clixon_cf +if [ $? -ne 0 ]; then + err "kill backend" +fi