diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ae6f50..c5cc8435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ * Added clixon_util_regexp utility function * Added extensive regexp test [test/test_pattern.sh] for both posix and libxml2 * Added regex cache to type resolution +* Yang "refine" feature supported + * According to RFC 7950 7.13.2 * Yang "min-element" and "max-element" feature supported * According to RFC 7950 7.7.4 and 7.7.5 * See (tests)[test/test_minmax.sh] diff --git a/README.md b/README.md index 0a6a90db..13778966 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Clixon follows: However, the following YANG syntax modules are not implemented (reference to RFC7950 in parenthesis): - deviation (7.20.3) - action (7.15) -- refine (7.13.2) +- augment in a uses sub-clause (7.17) (module-level augment is implemented) - status (7.21.2) - extension (7.19) - YIN (13) diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 3c16852c..8ecf3017 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -301,7 +301,38 @@ ys_free1(yang_stmt *ys) return 0; } -/*! Free a tree of yang statements recursively */ +/*! Remove child i from parent yp (dont free) + * @param[in] yp Parent node + * @param[in] i Order of child to remove + * @retval NULL No such node, nothing done + * @retval yc returned orphaned yang node + * @see ys_free Deallocate yang node + * @note Do not call this in a loop of yang children (unless you know what you are doing) + */ +static yang_stmt * +ys_prune(yang_stmt *yp, + int i) +{ + size_t size; + yang_stmt *yc = NULL; + + if (i >= yp->ys_len) + goto done; + size = (yp->ys_len - i - 1)*sizeof(struct yang_stmt *); + yc = yp->ys_stmt[i]; + memmove(&yp->ys_stmt[i], + &yp->ys_stmt[i+1], + size); + yc = yp->ys_stmt[yp->ys_len--] = NULL;; + done: + return yc; +} + +/*! 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 + * @see ys_prune Remove from parent + */ int ys_free(yang_stmt *ys) { @@ -1363,7 +1394,6 @@ ys_populate_leaf(clicon_handle h, /* 3b. If not default value, indicate empty cv. */ cv_flag_set(cv, V_UNSET); /* no value (no default) */ } - /* 4. Check if leaf is part of list, if key exists mark leaf as key/unique */ if (yparent && yparent->ys_keyword == Y_LIST){ if ((ret = yang_key_match(yparent, ys->ys_argument)) < 0) @@ -1822,23 +1852,25 @@ ys_populate_unknown(clicon_handle h, /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * - * We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree - * See ys_parse_sub for first pass and what can be assumed + * @param[in] ys Yang statement + * @param[in] h Clicon handle + * Preferably run this command using yang_apply + * Done in 2nd pass after complete parsing to be sure to have a complete + * parse-tree + * After this pass, cv:s are set for LEAFs and LEAF-LISTs + * @see ys_parse_sub for first pass and what can be assumed + * @see ys_populate2 for after grouping expand and augment + * (there may be more functions (all?) that may be moved to ys_populate2) */ int ys_populate(yang_stmt *ys, void *arg) { - int retval = -1; + int retval = -1; clicon_handle h = (clicon_handle)arg; switch(ys->ys_keyword){ - case Y_LEAF: - case Y_LEAF_LIST: - if (ys_populate_leaf(h, ys) < 0) - goto done; - break; case Y_LIST: if (ys_populate_list(h, ys) < 0) goto done; @@ -1851,11 +1883,6 @@ ys_populate(yang_stmt *ys, if (ys_populate_length(h, ys) < 0) goto done; break; - case Y_MANDATORY: /* call yang_mandatory() to check if set */ - case Y_CONFIG: - if (ys_parse(ys, CGV_BOOL) == NULL) - goto done; - break; case Y_TYPE: if (ys_populate_type(h, ys) < 0) goto done; @@ -1880,9 +1907,42 @@ ys_populate(yang_stmt *ys, return retval; } +/*! Run after grouping expand and augment + * @see ys_populate run before grouping expand and augment + */ +static int +ys_populate2(yang_stmt *ys, + void *arg) +{ + int retval = -1; + clicon_handle h = (clicon_handle)arg; + + switch(ys->ys_keyword){ + case Y_LEAF: + case Y_LEAF_LIST: + if (ys_populate_leaf(h, ys) < 0) + goto done; + break; + case Y_MANDATORY: /* call yang_mandatory() to check if set */ + case Y_CONFIG: + if (ys_parse(ys, CGV_BOOL) == NULL) + goto done; + break; + default: + break; + } + retval = 0; + done: + return retval; +} + /*! Resolve a grouping name from a point in the yang tree - * @retval 0 OK, but ygrouping determines if a grouping was resolved or not - * @retval -1 Error, with clicon_err called + * @param[in] ys Yang statement of "uses" statement doing the lookup + * @param[in] prefix Prefix of grouping to look for + * @param[in] name Name of grouping to look for + * @param[out] ygrouping0 A found grouping yang structure as result + * @retval 0 OK, ygrouping may be NULL + * @retval -1 Error, with clicon_err called */ static int ys_grouping_resolve(yang_stmt *ys, @@ -1999,23 +2059,94 @@ yang_augment_spec(yang_stmt *ysp, return retval; } -/*! Macro expansion of grouping/uses done in step 2 of yang parsing - NOTE - RFC6020 says this: - Identifiers appearing inside the grouping are resolved relative to the scope in which the - grouping is defined, not where it is used. Prefix mappings, type names, grouping - names, and extension usage are evaluated in the hierarchy where the - "grouping" statement appears. - But it will be very difficult to generate keys etc with this semantics. So for now I - macro-expand them -*/ +/*! Given a refine node, perform the refinement action on the target refine node + * The RFC is somewhat complicate in the rules for refine. + * Most nodes will be replaced, but some are added + * @param[in] yr Refine node + * @param[in] yt Refine target node (will be modified) + * @see RFC7950 Sec 7.13.2 + * There may be some missed cornercases + */ static int -yang_expand(yang_stmt *yn) +ys_do_refine(yang_stmt *yr, + yang_stmt *yt) +{ + int retval = -1; + yang_stmt *yrc; /* refine child */ + yang_stmt *yrc1; + yang_stmt *ytc; /* target child */ + enum rfc_6020 keyw; + int i; + + /* Loop through refine node children. First if remove do that first + * In some cases remove a set of nodes. + */ + yrc = NULL; + while ((yrc = yn_each(yr, yrc)) != NULL) { + keyw = yang_keyword_get(yrc); + switch (keyw){ + case Y_DEFAULT: /* remove old, add new */ + case Y_DESCRIPTION: + case Y_REFERENCE: + case Y_CONFIG: + case Y_MANDATORY: + case Y_PRESENCE: + case Y_MIN_ELEMENTS: + case Y_MAX_ELEMENTS: + case Y_EXTENSION: + /* Remove old matching, dont increment due to prune in loop */ + for (i=0; iys_len; ){ + ytc = yt->ys_stmt[i]; + if (keyw != yang_keyword_get(ytc)){ + i++; + continue; + } + ys_prune(yt, i); + ys_free(ytc); + } + /* fall through and add if not found */ + case Y_MUST: /* keep old, add new */ + case Y_IF_FEATURE: + break; + default: + break; + } + } + /* Second, add the node(s) */ + yrc = NULL; + while ((yrc = yn_each(yr, yrc)) != NULL) { + keyw = yang_keyword_get(yrc); + /* Make copy */ + if ((yrc1 = ys_dup(yrc)) == NULL) + goto done; + if (yn_insert(yt, yrc1) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Macro expansion of grouping/uses done in step 2 of yang parsing + * RFC7950: + * Identifiers appearing inside the grouping are resolved + * relative to the scope in which the grouping is defined, not where it is + * used. Prefix mappings, type names, grouping names, and extension usage are + * evaluated in the hierarchy where the "grouping" statement appears. + * The identifiers defined in the grouping are not bound to a namespace + * until the contents of the grouping are added to the schema tree via a + * "uses" statement that does not appear inside a "grouping" statement, + * at which point they are bound to the namespace of the current module. + */ +static int +yang_expand_grouping(yang_stmt *yn) { int retval = -1; yang_stmt *ys = NULL; - yang_stmt *ygrouping; - yang_stmt *yg; + yang_stmt *ygrouping; /* grouping original */ + yang_stmt *ygrouping2; /* grouping copy */ + yang_stmt *yg; /* grouping child */ + yang_stmt *yr; /* refinement */ int glen; int i; int j; @@ -2034,20 +2165,21 @@ yang_expand(yang_stmt *yn) prefix = yarg_prefix(ys); /* And this its prefix */ if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0) goto done; - + if (prefix){ + free(prefix); + prefix = NULL; + } if (ygrouping == NULL){ clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"", __FUNCTION__, ys->ys_argument, ys_module(ys)->ys_argument); goto done; break; } - if (prefix) - free(prefix); /* XXX move up */ /* Check mark flag to see if this grouping (itself) has been expanded If not, this needs to be done before we can insert it into the 'uses' place */ if ((ygrouping->ys_flags & YANG_FLAG_MARK) == 0){ - if (yang_expand(ygrouping) < 0) + if (yang_expand_grouping(ygrouping) < 0) goto done; ygrouping->ys_flags |= YANG_FLAG_MARK; /* Mark as expanded */ } @@ -2072,14 +2204,41 @@ yang_expand(yang_stmt *yn) &yn->ys_stmt[i+1], size); } + + /* Make a copy of the while grouping making it easier to + * refine it */ + if ((ygrouping2 = ys_dup(ygrouping)) == NULL) + goto done; + /* Iterate through refinments and modify grouping copy + * See RFC 7950 7.13.2 yrt is the refine target node + */ + yr = NULL; + while ((yr = yn_each(ys, yr)) != NULL) { + yang_stmt *yrt; /* refine target node */ + if (yang_keyword_get(yr) != Y_REFINE) + continue; + /* Find a node */ + if (yang_desc_schema_nodeid(ygrouping2, + yang_argument_get(yr), + -1, + &yrt) < 0) + goto done; + /* Not found, try next */ + if (yrt == NULL) + continue; + /* Do the actual refinement */ + if (ys_do_refine(yr, yrt) < 0) + goto done; + /* RFC: The argument is a string that identifies a node in the + * grouping. I interpret that as only one node --> break */ + break; + } /* Then copy and insert each child element */ for (j=0; jys_stmt[j])) == NULL) - goto done; + yg = ygrouping2->ys_stmt[j]; /* Child of refined copy */ yn->ys_stmt[i+j] = yg; yg->ys_parent = yn; } - /* XXX: refine */ /* Remove 'uses' node */ ys_free(ys); break; /* Note same child is re-iterated since it may be changed */ @@ -2091,7 +2250,7 @@ yang_expand(yang_stmt *yn) /* Second pass since length may have changed */ for (i=0; iys_len; i++){ ys = yn->ys_stmt[i]; - if (yang_expand(ys) < 0) + if (yang_expand_grouping(ys) < 0) goto done; } retval = 0; @@ -2609,15 +2768,20 @@ yang_parse_post(clicon_handle h, /* 6: Macro expansion of all grouping/uses pairs. Expansion needs marking */ for (i=modnr; iys_len; i++){ - if (yang_expand(yspec->ys_stmt[i]) < 0) + if (yang_expand_grouping(yspec->ys_stmt[i]) < 0) goto done; yang_apply(yspec->ys_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); } - /* 7: Top-level augmentation of all modules XXX: only new modules? */ + /* 7: Top-level augmentation of all modules. (Augment also in uses) */ if (yang_augment_spec(yspec, modnr) < 0) goto done; + /* 4: Go through parse tree and do 2nd step populate (eg default) */ + for (i=modnr; iys_len; i++) + if (yang_apply(yspec->ys_stmt[i], -1, ys_populate2, (void*)h) < 0) + goto done; + /* 8: sanity check of schemanode references, need more here */ for (i=modnr; iys_len; i++) if (yang_apply(yspec->ys_stmt[i], -1, ys_schemanode_check, NULL) < 0) @@ -2829,19 +2993,13 @@ yang_spec_load_dir(clicon_handle h, * This is a failsafe in case anything else fails */ if (revm && rev0){ - int size; if (revm > rev0) /* Loaded module is older or eq -> remove ym */ ym = ym0; for (j=0; jys_len; j++) if (yspec->ys_stmt[j] == ym) break; - size = (yspec->ys_len - j - 1)*sizeof(struct yang_stmt *); - memmove(&yspec->ys_stmt[j], - &yspec->ys_stmt[j+1], - size); + ys_prune(yspec, j); ys_free(ym); - yspec->ys_len--; - yspec->ys_stmt[yspec->ys_len] = NULL; } } if (yang_parse_post(h, yspec, modnr) < 0) @@ -3101,7 +3259,7 @@ yang_abs_schema_nodeid(yang_stmt *yspec, * @param[out] yres First yang node matching schema nodeid * @retval 0 OK * @retval -1 Error, with clicon_err called - * @see yang_schema_nodeid + * @see yang_abs_schema_nodeid * Used in yang: unique, refine, uses augment */ int diff --git a/test/test_augment.sh b/test/test_augment.sh index b7302270..a3a9d7ef 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -22,6 +22,7 @@ cat < $cfg a:test $dir /usr/local/share/clixon + $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME @@ -66,14 +67,14 @@ module ietf-interfaces { } } grouping endpoint { - description "A reusable endpoint group."; - leaf mip { + description "A reusable endpoint group. From rf7950 Sec 7.12.2"; + leaf ip { type string; } - leaf mport { + leaf port { type uint16; } - } + } } EOF @@ -98,12 +99,14 @@ module example-augment { identity you { base my-type; } - grouping mypoint { - description "A reusable endpoint group."; - leaf ip { - type string; + grouping localgroup { + description "Local grouping defining lid and lport"; + leaf lid { + description "this will be kept as-is"; + type string; } - leaf port { + leaf lport { + description "this will be refined"; type uint16; } } @@ -124,12 +127,14 @@ module example-augment { } } uses if:endpoint { + description "Use an external grouping defining ip and port"; refine port { default 80; } } - uses mypoint { - refine mport { + uses localgroup { + description "Use a local grouping defining lip and lport"; + refine lport { default 8080; } } @@ -137,37 +142,35 @@ module example-augment { } EOF -new "test params: -f $cfg -y $fyang" +new "test params: -f $cfg" if [ $BE -ne 0 ]; then new "kill old backend" - sudo clixon_backend -zf $cfg -y $fyang + sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi - new "start backend -s init -f $cfg -y $fyang" - start_backend -s init -f $cfg -y $fyang + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg new "waiting" sleep $RCWAIT fi # mandatory-leaf See RFC7950 Sec 7.17 -# Error1: the xml should have xmlns for "mymod" -# XMLNS_YANG_ONLY must be undeffed new "netconf set interface with augmented type and mandatory leaf" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' +expecteof "$clixon_netconf -qf $cfg" 0 ' e1 mymod:some-new-iftype true ]]>]]>' "^]]>]]>$" -new "netconf validate ok" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' +new "netconf verify get with refined ports" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^e1mymod:some-new-iftypetrue808080]]>]]>$' new "netconf set identity defined in other" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' +expecteof "$clixon_netconf -qf $cfg" 0 ' e2 fddi @@ -176,10 +179,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf validate ok" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^]]>]]>$' new "netconf set identity defined in main" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' +expecteof "$clixon_netconf -qf $cfg" 0 ' e3 fddi @@ -188,9 +191,11 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf validate ok" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^]]>]]>$' + +new "discard" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" if [ $BE -eq 0 ]; then exit # BE