diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e7dce0..f7e8cd22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,8 @@ Expected: June 2021 * Yang Deviation/deviate [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) * See RFC7950 Sec 5.6.3 - * Work-in-progress + * Implemented: "not-supported" and "add" + * Not yet: "replace" and "delete" ### Minor features diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index c964a4b6..9f06e179 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -238,12 +238,12 @@ int yang_order(yang_stmt *y); int yang_print_cb(FILE *f, yang_stmt *yn, clicon_output_cb *fn); int yang_print(FILE *f, yang_stmt *yn); int yang_print_cbuf(cbuf *cb, yang_stmt *yn, int marginal); +int yang_deviation(yang_stmt *ys, void *arg); int yang_spec_dump(yang_stmt *yspec, int debuglevel); int if_feature(yang_stmt *yspec, char *module, char *feature); int ys_populate(yang_stmt *ys, void *arg); int ys_populate2(yang_stmt *ys, void *arg); -int yang_apply(yang_stmt *yn, enum rfc_6020 key, yang_applyfn_t fn, - void *arg); +int yang_apply(yang_stmt *yn, enum rfc_6020 key, yang_applyfn_t fn, int from, void *arg); int yang_datanode(yang_stmt *ys); int yang_abs_schema_nodeid(yang_stmt *ys, char *schema_nodeid, yang_stmt **yres); int yang_desc_schema_nodeid(yang_stmt *yn, char *schema_nodeid, yang_stmt **yres); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index f866418d..e9f8e620 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -513,6 +513,40 @@ ys_prune(yang_stmt *yp, return yc; } +/*! Remove yang node from parent (dont free + * @param[in] ys Yang node to remove + * @retval 0 OK + * @retval -1 Error + * @see ys_prune if parent and position is known + * @note Do not call this in a loop of yang children (unless you know what you are doing) + */ +static int +ys_prune_self(yang_stmt *ys) +{ + int retval = -1; + yang_stmt *yp; + yang_stmt *yc; + int i; + + if ((yp = yang_parent_get(ys)) != NULL){ + yc = NULL; + i = 0; + /* Find order of ys in child-list */ + while ((yc = yn_each(yp, yc)) != NULL) { + if (ys == yc) + break; + i++; + } + if (yc != NULL){ + assert(yc == ys); + ys_prune(yp, i); + } + } + retval = 0; + // done: + return retval; +} + /*! Free a yang statement tree recursively * @param[in] ys Yang node to remove and all its children recursively * @note does not remove yang node from tree @@ -1555,6 +1589,105 @@ yang_print_cbuf(cbuf *cb, return 0; } +/*! Yang deviation/deviate + * + * Identify deviation target node, and go through all its deviate statements. + * Features/if-feature must have run before + * @param[in] ys The yang deviation to populate. + * @param[in] h Clicon handle + * @see RFC 7950 5.6.3 and 7.20.3 + */ +int +yang_deviation(yang_stmt *ys, + void *arg) + +{ + int retval = -1; + char *nodeid; + yang_stmt *ytarget = NULL; + yang_stmt *yd; + yang_stmt *yc; + yang_stmt *yc1; + char *devop; + clicon_handle h = (clicon_handle)arg; + enum rfc_6020 kw; + int min; + int max; + + if (yang_keyword_get(ys) != Y_DEVIATION) + goto ok; + /* Absolute schema node identifier identifying target node */ + if ((nodeid = yang_argument_get(ys)) == NULL){ + clicon_err(OE_YANG, EINVAL, "No argument to deviation"); + goto done; + } + /* Get target node */ + if (yang_abs_schema_nodeid(ys, nodeid, &ytarget) < 0) + goto done; + if (ytarget == NULL){ + goto ok; + /* The RFC does not explicitly say the target node MUST exist + clicon_err(OE_YANG, 0, "schemanode sanity check of %s", nodeid); + goto done; + */ + } + /* Go through deviates of deviation */ + yd = NULL; + while ((yd = yn_each(ys, yd)) != NULL) { + /* description / if-feature / reference */ + if (yang_keyword_get(yd) != Y_DEVIATE) + continue; + devop = yang_argument_get(yd); + if (strcmp(devop, "not-supported") == 0){ + if (ys_prune_self(ytarget) < 0) + goto done; + if (ys_free(ytarget) < 0) + goto done; + goto ok; /* Target node removed, no other deviates possible */ + } + else if (strcmp(devop, "add") == 0){ + yc = NULL; + while ((yc = yn_each(yd, yc)) != NULL) { + /* If a property can only appear once, the property MUST NOT + exist in the target node. */ + kw = yang_keyword_get(yc); + if (yang_find(ytarget, kw, NULL) != NULL){ + if (yang_cardinality_interval(h, + yang_keyword_get(ytarget), + kw, + &min, + &max) < 0) + goto done; + if (max == 1){ + clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" added but node already exist in target %s", + nodeid, + yang_key2str(kw), yang_argument_get(yc), + yang_argument_get(ytarget)); + goto done; + } + } + /* Make a copy of deviate child and insert. */ + if ((yc1 = ys_dup(yc)) == NULL) + goto done; + if (yn_insert(ytarget, yc1) < 0) + goto done; + } + } + else if (strcmp(devop, "replace") == 0){ + } + else if (strcmp(devop, "delete") == 0){ + } + else{ /* Shouldnt happen, lex/yacc takes it */ + clicon_err(OE_YANG, EINVAL, "%s: invalid deviate operator", devop); + goto done; + } + } + ok: + retval = 0; + done: + return retval; +} + /*! Populate yang leafs after parsing. Create cv and fill it in. * * Populate leaf in 2nd round of yang parsing, now that context is complete: @@ -2507,6 +2640,7 @@ yang_features(clicon_handle h, * @param[in] yn yang node * @param[in] key yang keyword to use as filer or -1 for all * @param[in] fn Callback + * @param[in] depth Depth argument: where to start. If <=0 call the calling node yn, if 1 start with its children, etc * @param[in] arg Argument * @retval -1 Error, aborted at first error encounter * @retval 0 OK, all nodes traversed @@ -2516,7 +2650,7 @@ yang_features(clicon_handle h, * { * return 0; * } - * yang_apply(ys, Y_TYPE, ys_fn, NULL); + * yang_apply(ys, Y_TYPE, ys_fn, 1, NULL); // Call all yn:s children recursively * @endcode * @note do not delete or move around any children during this function */ @@ -2524,6 +2658,7 @@ int yang_apply(yang_stmt *yn, enum rfc_6020 keyword, yang_applyfn_t fn, + int depth, void *arg) { int retval = -1; @@ -2531,17 +2666,19 @@ yang_apply(yang_stmt *yn, int i; int ret; - for (i=0; iys_len; i++){ - ys = yn->ys_stmt[i]; - if ((int)keyword == -1 || keyword == ys->ys_keyword){ - if ((ret = fn(ys, arg)) < 0) + if (depth <= 0){ + if ((int)keyword == -1 || keyword == yn->ys_keyword){ + if ((ret = fn(yn, arg)) < 0) goto done; if (ret > 0){ retval = ret; goto done; } } - if ((ret = yang_apply(ys, keyword, fn, arg)) < 0) + } + for (i=0; iys_len; i++){ + ys = yn->ys_stmt[i]; + if ((ret = yang_apply(ys, keyword, fn, depth-1, arg)) < 0) goto done; if (ret > 0){ retval = ret; @@ -2551,7 +2688,7 @@ yang_apply(yang_stmt *yn, retval = 0; done: return retval; -} +} /*! Check if a node is a yang "data node" * @param[in] ys Yang statement node diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index aa3f589a..99e39a2b 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -459,7 +459,6 @@ ycard_find(enum rfc_6020 parent, enum rfc_6020 child, const struct ycard *yclist, int p) - { const struct ycard *yc; @@ -490,12 +489,12 @@ yang_cardinality(clicon_handle h, yang_stmt *yt, char *modname) { - int retval = -1; - yang_stmt *ys = NULL; - int pk; - int ck; - int i; - int nr; + int retval = -1; + yang_stmt *ys = NULL; + int pk; + int ck; + int i; + int nr; const struct ycard *ycplist; /* ycard parent table*/ const struct ycard *yc; @@ -556,3 +555,32 @@ yang_cardinality(clicon_handle h, return retval; } +/*! Return cardinality interval [min,max] given yang parent and child keyword. + * + * @param[in] h Clicon handle + * @param[in] parent_key + * @param[in] child_key + * @param[out] minp 0 or 1 + * @param[out] maxp 1 or NMAX (large number) + */ +int +yang_cardinality_interval(clicon_handle h, + enum rfc_6020 parent_key, + enum rfc_6020 child_key, + int *min, + int *max) +{ + int retval = -1; + const struct ycard *ycplist; /* ycard parent table*/ + + if ((ycplist = ycard_find(parent_key, child_key, yclist, 0)) == NULL){ + clicon_err(OE_YANG, EINVAL, "keys %d %d do not have cardinality", + parent_key, child_key); + goto done; + } + *min = ycplist->yc_min; + *max = ycplist->yc_max; + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_yang_cardinality.h b/lib/src/clixon_yang_cardinality.h index 27c0bd3f..ed3b350f 100644 --- a/lib/src/clixon_yang_cardinality.h +++ b/lib/src/clixon_yang_cardinality.h @@ -40,4 +40,6 @@ */ int yang_cardinality(clicon_handle h, yang_stmt *yt, char *modname); +int yang_cardinality_interval(clicon_handle h, enum rfc_6020 parent_key, enum rfc_6020 child_key, int *min, int *max); + #endif /* _CLIXON_YANG_CARDINALITY_H_ */ diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index f42f4d2f..553b3a47 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -1074,14 +1074,6 @@ ys_schemanode_check(yang_stmt *ys, } break; } - case Y_DEVIATION: - if (yang_abs_schema_nodeid(ys, arg, &yres) < 0) - goto done; - if (yres == NULL){ - clicon_err(OE_YANG, 0, "schemanode sanity check of %s", arg); - goto done; - } - break; default: break; } @@ -1330,21 +1322,27 @@ yang_parse_post(clicon_handle h, for (i=modmin; iSept17]]>]]>" "applicationunknown-elementdaytimeerrorFailed to find YANG spec of XML node: daytime with parent: system in namespace: urn:example:base]]>]]" + else + new "Add example-base daytime - supported" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOSept17]]>]]>" "]]>]]" + + new "Add user bob" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLObob]]>]]>" "]]>]]" + + new "netconf commit" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + + if $admindefault; then + new "Get type admin expected" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^bobadmin]]>]]>$" +# XXX Cannot select a default value?? +# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" foo + else + new "Get type none expected" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + fi + fi + if [ "$BE" -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f "$cfg" + fi +} # testrun + # Example from RFC 7950 Sec 7.20.3.3 cat < $fyangdev -module $APPNAME{ +module example-deviations{ yang-version 1.1; prefix md; namespace "urn:example:deviations"; - import example-base { prefix base; } +} +EOF +new "daytime supported" +testrun true false +# Example from RFC 7950 Sec 7.20.3.3 +cat < $fyangdev +module example-deviations{ + yang-version 1.1; + prefix md; + namespace "urn:example:deviations"; + import example-base { + prefix base; + } deviation /base:system/base:daytime { - deviate not-supported; - } - deviation /base:system/base:user/base:type { - deviate add { - default "admin"; // new users are 'admin' by default - } - } - deviation /base:system { - deviate delete { - must "daytime or time"; - } + deviate not-supported; } } EOF +new "daytime not supported" +testrun false false -new "test params: -f $cfg" - -if [ "$BE" -ne 0 ]; then - new "kill old backend" - sudo clixon_backend -zf "$cfg" - if [ $? -ne 0 ]; then - err - fi - new "start backend -s init -f $cfg" - start_backend -s init -f "$cfg" - - new "waiting" - wait_backend -fi - - -if [ "$BE" -eq 0 ]; then - exit # BE -fi - -new "Kill backend" -# Check if premature kill -pid=$(pgrep -u root -f clixon_backend) -if [ -z "$pid" ]; then - err "backend already dead" -fi -# kill backend -stop_backend -f "$cfg" +# Example from RFC 7950 Sec 7.20.3.3 +cat < $fyangdev +module example-deviations{ + yang-version 1.1; + prefix md; + namespace "urn:example:deviations"; + import example-base { + prefix base; + } + deviation /base:system/base:user/base:type { + deviate add { + default "admin"; // new users are 'admin' by default + } + } +} +EOF +new "deviate add, check admin default" +testrun true true rm -rf "$dir"