diff --git a/CHANGELOG.md b/CHANGELOG.md index 851dba14..95f7d3e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,13 @@ Expected: June 2021 ### New features -* Yang deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) +* YANG when statement in conjunction with grouping/uses/augment + * Several cases were not implemented fully according to RFC 7950: + * Do not extend default values if when statements evaluate to false + * Do not allow edit-config of nodes if when statements evaluate to false (Sec 8.3.2) + * If a key leaf is defined in a grouping that is used in a list, the "uses" statement MUST NOT have a "when" statement. (See 7.21.5) + * See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/2$ +* YANG deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) * See RFC7950 Sec 5.6.3 ### API changes on existing protocol/config features diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index b29bdc25..95383db9 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1080,7 +1080,7 @@ restconf_connection(int s, struct evbuffer *ev; size_t buflen; char *buf = NULL; - + if ((rc = conn->arg) == NULL){ clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen"); goto done; @@ -1837,12 +1837,9 @@ restconf_sig_term(int arg) { static int i=0; - clicon_debug(1, "%s", __FUNCTION__); - if (i++ == 0){ - clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", - __PROGRAM__, __FUNCTION__, getpid(), arg); - } - else + clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", + __PROGRAM__, __FUNCTION__, getpid(), arg); + if (i++ > 0) /* Allow one sigterm before proper exit */ exit(-1); /* This should ensure no more accepts or incoming packets are processed because next time eventloop * is entered, it will terminate. diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 9238372a..98084114 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -380,7 +380,7 @@ api_operations(clicon_handle h, int pretty, restconf_media media_out) { - int retval; + int retval = -1; cxobj *xerr = NULL; clicon_debug(1, "%s", __FUNCTION__); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index d016613d..39c4a8d1 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -144,10 +144,10 @@ typedef enum yang_bind yang_bind; typedef struct xml cxobj; /* struct defined in clicon_xml.c */ /*! Callback function type for xml_apply - * @retval -1 Error, aborted at first error encounter + * @retval -1 Error, aborted at first error encounter, return -1 to end user * @retval 0 OK, continue - * @retval 1 Abort, dont continue with others - * @retval 2 Locally, just abort this subtree, continue with others + * @retval 1 Abort, dont continue with others, return 1 to end user + * @retval 2 Locally abort this subtree, continue with others */ typedef int (xml_applyfn_t)(cxobj *x, void *arg); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 9f06e179..5d25249e 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -136,7 +136,7 @@ enum rfc_6020{ Y_UNKNOWN, Y_USES, Y_VALUE, - Y_WHEN, + Y_WHEN, /* See also ys_when_xpath / ys_when_nsc */ Y_YANG_VERSION, Y_YIN_ELEMENT, Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 5693f6c0..22733cfa 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -203,13 +203,79 @@ check_body_namespace(cxobj *x0, return retval; } +/*! Check yang when condition between a new xml x1 and old x0 + * + * check if there is a when condition. First try it on the new request (x1), then on the + * existing (x0). + * @param[in] x0p Parent of x0 + * @param[in] x1 XML tree which modifies base + * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL + * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0. + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK + * @note There may be some combination cases (x0+x1) that are not covered in this function. + */ +static int +check_when_condition(cxobj *x0p, + cxobj *x1, + yang_stmt *y0, + cbuf *cbret) +{ + int retval = -1; + char *xpath = NULL; + cvec *nsc = NULL; + int nr; + cxobj *x1p; + yang_stmt *y = NULL; + cbuf *cberr = NULL; + + if ((y = y0) != NULL || + (y = (yang_stmt*)xml_spec(x1)) != NULL){ + if ((xpath = yang_when_xpath_get(y)) != NULL){ + nsc = yang_when_nsc_get(y); + x1p = xml_parent(x1); + if ((nr = xpath_vec_bool(x1p, nsc, "%s", xpath)) < 0) /* Try request */ + goto done; + if (nr == 0){ + /* Try existing tree */ + if ((nr = xpath_vec_bool(x0p, nsc, "%s", xpath)) < 0) + goto done; + if (nr == 0){ + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "Node '%s' tagged with 'when' condition '%s' in module '%s' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)", + yang_argument_get(y), + xpath, + yang_argument_get(ys_module(y))); + if (netconf_unknown_element(cbret, "application", yang_argument_get(y), + cbuf_get(cberr)) < 0) + goto done; + goto fail; + } + } + } + } + retval = 1; + done: + if (cberr) + cbuf_free(cberr); + return retval; + fail: + retval = 0; + goto done; +} + /*! Modify a base tree x0 with x1 with yang spec y according to operation op * @param[in] h Clicon handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) - * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 + * @param[in] x0t * @param[in] x1 XML tree which modifies base * @param[in] x1t Request root node (nacm needs this) + * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[in] username User name of requestor for nacm * @param[in] xnacm NACM XML tree (only if !permit) @@ -264,6 +330,10 @@ text_modify(clicon_handle h, clicon_err(OE_XML, EINVAL, "x1 is missing"); goto done; } + if ((ret = check_when_condition(x0p, x1, y0, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; /* Check for operations embedded in tree according to netconf */ if ((ret = attr_ns_value(x1, "operation", NETCONF_BASE_NAMESPACE, cbret, &opstr)) < 0) diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 2b873eaf..cf3c7143 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -1227,7 +1227,7 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ + /* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ xpath = yang_argument_get(yc); /* "when" has xpath argument */ /* WHEN xpath needs namespace context */ @@ -1253,7 +1253,9 @@ xml_yang_validate_all(clicon_handle h, goto fail; } } - /* Augmented when using special struct. */ + /* Second variants of WHEN: + * Augmented and uses when using special info in node + */ if ((xpath = yang_when_xpath_get(ys)) != NULL){ if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys), "%s", xpath)) < 0) @@ -1263,7 +1265,7 @@ xml_yang_validate_all(clicon_handle h, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cb, "Failed augmented WHEN condition %s of node %s in module %s", + cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'", xpath, xml_name(xt), yang_argument_get(ys_module(ys))); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 33d2a468..af400177 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1142,6 +1142,8 @@ xml_default1(yang_stmt *yt, cxobj *xc; int top=0; /* Top symbol (set default namespace) */ int create = 0; + char *xpath; + int nr; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1162,6 +1164,13 @@ xml_default1(yang_stmt *yt, switch (yang_keyword_get(yc)){ case Y_LEAF: if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ + /* Check when statement from uses or augment */ + if ((xpath = yang_when_xpath_get(yc)) != NULL){ + if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) + goto done; + if (nr == 0) + break; /* Do not create default if xpath fails */ + } if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){ /* No such child exist, create this leaf */ if (xml_default_create(yc, xt, top) < 0) @@ -1172,6 +1181,13 @@ xml_default1(yang_stmt *yt, break; case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ + /* Check when statement from uses or augment */ + if ((xpath = yang_when_xpath_get(yc)) != NULL){ + if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) + goto done; + if (nr == 0) + break; /* Do not create default if xpath fails */ + } /* If this is non-presence, (and it does not exist in xt) call * recursively and create nodes if any default value exist first. * Then continue and populate? diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 45bc6eb4..8ed7c331 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -3192,7 +3192,7 @@ yang_container_cli_hide(yang_stmt *ys, /*! Check if yang node yn has key-stmt as child which matches name * * The function looks at the LIST argument string (not actual children) - * @param[in] yn Yang node with sub-statements (look for a key child) + * @param[in] yn Yang list node with sub-statements (look for a key child) * @param[in] name Check if this name (eg "b") is a key in the yang key statement * * @retval -1 Error diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index a2da9248..d14e98ac 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -90,8 +90,8 @@ struct yang_stmt{ types as : list */ yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ - char *ys_when_xpath; /* Special conditional for a "when"-associated augment xpath */ - cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment namespace ctx */ + char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ + cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */ int _ys_vector_i; /* internal use: yn_each */ }; diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index ad85b9ac..629860e4 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -104,6 +104,9 @@ /* Size of json read buffer when reading from file*/ #define BUFLEN 1024 +/* Forward */ +static int yang_expand_grouping(yang_stmt *yn); + /*! Resolve a grouping name from a module, includes looking in submodules */ static yang_stmt * @@ -340,7 +343,9 @@ yang_augment_node(yang_stmt *ys) if (yn_insert(ytarget, yc) < 0) goto done; - /* If there is an associated when statement, add a special when struct to the yang */ + /* If there is an associated when statement, add a special when struct to the yang + * see xml_yang_validate_all + */ if (ywhen){ if (yang_when_xpath_set(yc, wxpath) < 0) goto done; @@ -454,6 +459,208 @@ ys_do_refine(yang_stmt *yr, return retval; } +/*! Yang node yg is a leaf in yang node list yn + * Could be made to a generic function used elsewhere as well + * @param[in] y Yang leaf + * @param[in] yp Yang list parent + * @retval 0 No, y is not a key leaf in list yp + * @retval 1 Yes, y is a key leaf in list yp + */ +static int +ys_iskey(yang_stmt *y, + yang_stmt *yp) +{ + cvec *cvv; + cg_var *cv; + char *name; + + if (yang_keyword_get(y) != Y_LEAF) + return 0; + if (yang_keyword_get(yp) != Y_LIST) + return 0; + if ((cvv = yang_cvec_get(yp)) == NULL) + return 0; + name = yang_argument_get(y); + cv = NULL; + while ((cv = cvec_each(cvv, cv)) != NULL) { + if (strcmp(name, cv_string_get(cv)) == 0) + return 1; + } + return 0; +} + + +/*! Helper function to yang_expand_grouping + * @param[in] yn Yang parent node of uses ststement + * @param[in] ys Uses statement + * @retval 0 OK + * @retval -1 Error + */ +static int +yang_expand_uses_node(yang_stmt *yn, + yang_stmt *ys, + int i) +{ + int retval = -1; + char *id = NULL; + char *prefix = NULL; + yang_stmt *ygrouping; /* grouping original */ + yang_stmt *ygrouping2; /* grouping copy */ + yang_stmt *yg; /* grouping child */ + yang_stmt *yr; /* refinement */ + yang_stmt *yp; + int glen; + size_t size; + int j; + int k; + yang_stmt *ywhen; + char *wxpath = NULL; /* xpath of when statement */ + cvec *wnsc = NULL; /* namespace context of when statement */ + + /* Split argument into prefix and name */ + if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0) + goto done; + if (ys_grouping_resolve(ys, prefix, id, &ygrouping) < 0) + goto done; + if (ygrouping == NULL){ + clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"", + __FUNCTION__, yang_argument_get(ys), yang_argument_get(ys_module(ys))); + goto done; + } + /* Check so that this uses statement is not a descendant of the grouping + */ + yp = yn; + do { + if (yp == ygrouping){ + clicon_err(OE_YANG, EFAULT, "Yang use of grouping %s in module %s is defined inside the grouping (recursion), see RFC 7950 Sec 7.12: A grouping MUST NOT reference itself", + yang_argument_get(ys), + yang_argument_get(ys_module(yn)) + ); + goto done; + } + } while((yp = yang_parent_get(yp)) != NULL); + if (yang_flag_get(ygrouping, YANG_FLAG_MARK) == 0){ + /* Check mark flag to see if this grouping has been expanded before, + * here below in the traverse section + * A mark could be completely normal (several uses) or it could be a recursion. + */ + yang_flag_set(ygrouping, YANG_FLAG_MARK); /* Mark as (being) expanded */ + if (yang_expand_grouping(ygrouping) < 0) + goto done; + } + /* Make a copy of the grouping, then make refinements to this copy + * Note this ygrouping2 object does not gave a parent and does not work in many + * functions which assume a full hierarchy, use the original ygrouping in those cases. + */ + if ((ygrouping2 = ys_dup(ygrouping)) == NULL) + goto done; + + /* Only replace data/schemanodes and unknowns: + * Compute the number of such nodes, and extend the child vector with that below + */ + glen = 0; + yg = NULL; + while ((yg = yn_each(ygrouping2, yg)) != NULL) { + if (yang_schemanode(yg) || yang_keyword_get(yg) == Y_UNKNOWN) + glen++; + } + /* + * yn is parent: the children of ygrouping replaces ys. + * Is there a case when glen == 0? YES AND THIS BREAKS + */ + if (glen != 1){ + size = (yang_len_get(yn) - i - 1)*sizeof(struct yang_stmt *); + yn->ys_len += glen - 1; + if (glen && (yn->ys_stmt = realloc(yn->ys_stmt, (yang_len_get(yn))*sizeof(yang_stmt *))) == 0){ + clicon_err(OE_YANG, errno, "realloc"); + goto done; + } + /* Then move all existing elements up from i+1 (not uses-stmt) */ + if (size) + memmove(&yn->ys_stmt[i+glen], + &yn->ys_stmt[i+1], + size); + } + /* Find when statement, if present */ + if ((ywhen = yang_find(ys, Y_WHEN, NULL)) != NULL){ + wxpath = yang_argument_get(ywhen); + if (xml_nsctx_yang(ywhen, &wnsc) < 0) + goto done; + } + /* Iterate through refinements 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(ygrouping, /* Cannot use ygrouping2 */ + yang_argument_get(yr), + &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; + } /* while yr */ + /* Then copy and insert each child element from ygrouping2 to yn */ + k=0; + for (j=0; jys_stmt[j]; /* Child of refined copy */ + /* Only replace data/schemanodes */ + if (!yang_schemanode(yg) && yang_keyword_get(yg) != Y_UNKNOWN){ + ys_free(yg); + continue; + } + /* If there is an associated when statement, add a special when struct to the yang + * see xml_yang_validate_all + */ + if (ywhen){ + if (ys_iskey(yg, yn)){ + /* RFC 7950 Sec 7.21.5: + * If a key leaf is defined in a grouping that is used in a list, the + * "uses" statement MUST NOT have a "when" statement. + */ + + clicon_err(OE_YANG, 0, "Key leaf '%s' defined in grouping '%s' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5", + yang_argument_get(yg), + yang_argument_get(ygrouping) + ); + goto done; + } + if (yang_when_xpath_set(yg, wxpath) < 0) + goto done; + if (yang_when_nsc_set(yg, wnsc) < 0) + goto done; + } + yn->ys_stmt[i+k] = yg; + yg->ys_parent = yn; + k++; + } + /* Remove 'uses' node */ + ys_free(ys); + /* Remove the grouping copy */ + ygrouping2->ys_len = 0; /* Cant do with get access function */ + ys_free(ygrouping2); + + retval = 0; + done: + if (wnsc) + cvec_free(wnsc); + if (prefix) + free(prefix); + if (id) + free(id); + return retval; +} + /*! Macro expansion of grouping/uses done in step 2 of yang parsing * RFC7950: * Identifiers appearing inside the grouping are resolved @@ -464,24 +671,16 @@ ys_do_refine(yang_stmt *yr, * 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. + * @param[in] yn Yang node for recursive iteration + * @retval 0 OK + * @retval -1 Error */ static int yang_expand_grouping(yang_stmt *yn) { int retval = -1; yang_stmt *ys = NULL; - 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; - int k; - char *id = NULL; - char *prefix = NULL; - size_t size; - yang_stmt *yp; /* Cannot use yang_apply here since child-list is modified (is destructive) */ i = 0; @@ -489,120 +688,8 @@ yang_expand_grouping(yang_stmt *yn) ys = yn->ys_stmt[i]; switch (yang_keyword_get(ys)){ case Y_USES: - /* Split argument into prefix and name */ - if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0) + if (yang_expand_uses_node(yn, ys, i) < 0) goto done; - if (ys_grouping_resolve(ys, prefix, id, &ygrouping) < 0) - goto done; - if (prefix){ - free(prefix); - prefix = NULL; - } - if (id){ - free(id); - id = NULL; - } - if (ygrouping == NULL){ - clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"", - __FUNCTION__, yang_argument_get(ys), yang_argument_get(ys_module(ys))); - goto done; - break; - } - /* Check so that this uses statement is not a descendant of the grouping - */ - yp = yn; - do { - if (yp == ygrouping){ - clicon_err(OE_YANG, EFAULT, "Yang use of grouping %s in module %s is defined inside the grouping (recursion), see RFC 7950 Sec 7.12: A grouping MUST NOT reference itself", - yang_argument_get(ys), - yang_argument_get(ys_module(yn)) - ); - goto done; - } - } while((yp = yang_parent_get(yp)) != NULL); - if (yang_flag_get(ygrouping, YANG_FLAG_MARK) == 0){ - /* Check mark flag to see if this grouping has been expanded before, - * here below in the traverse section - * A mark could be completely normal (several uses) or it could be a recursion. - */ - yang_flag_set(ygrouping, YANG_FLAG_MARK); /* Mark as (being) expanded */ - if (yang_expand_grouping(ygrouping) < 0) - goto done; - } - /* Make a copy of the grouping, then make refinements to this copy - * Note this ygrouping2 object does not gave a parent and does not work in many - * functions which assume a full hierarchy, use the original ygrouping in those cases. - */ - if ((ygrouping2 = ys_dup(ygrouping)) == NULL) - goto done; - - /* Only replace data/schemanodes and unknowns: - * Compute the number of such nodes, and extend the child vector with that below - */ - glen = 0; - yg = NULL; - while ((yg = yn_each(ygrouping2, yg)) != NULL) { - if (yang_schemanode(yg) || yang_keyword_get(yg) == Y_UNKNOWN) - glen++; - } - /* - * yn is parent: the children of ygrouping replaces ys. - * Is there a case when glen == 0? YES AND THIS BREAKS - */ - if (glen != 1){ - size = (yang_len_get(yn) - i - 1)*sizeof(struct yang_stmt *); - yn->ys_len += glen - 1; - if (glen && (yn->ys_stmt = realloc(yn->ys_stmt, (yang_len_get(yn))*sizeof(yang_stmt *))) == 0){ - clicon_err(OE_YANG, errno, "realloc"); - goto done; - } - /* Then move all existing elements up from i+1 (not uses-stmt) */ - if (size) - memmove(&yn->ys_stmt[i+glen], - &yn->ys_stmt[i+1], - size); - } - /* Iterate through refinements 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(ygrouping, /* Cannot use ygrouping2 */ - yang_argument_get(yr), - &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 from ygrouping2 to yn */ - k=0; - for (j=0; jys_stmt[j]; /* Child of refined copy */ - /* Only replace data/schemanodes */ - if (!yang_schemanode(yg) && yang_keyword_get(yg) != Y_UNKNOWN){ - ys_free(yg); - continue; - } - yn->ys_stmt[i+k] = yg; - yg->ys_parent = yn; - k++; - } - /* Remove 'uses' node */ - ys_free(ys); - /* Remove the grouping copy */ - ygrouping2->ys_len = 0; /* Cant do with get access function */ - ys_free(ygrouping2); break; /* Note same child is re-iterated since it may be changed */ default: i++; @@ -630,10 +717,6 @@ yang_expand_grouping(yang_stmt *yn) } retval = 0; done: - if (prefix) - free(prefix); - if (id) - free(id); return retval; } diff --git a/test/test_augment.sh b/test/test_augment.sh index b3a32f78..dbfe424e 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -199,11 +199,10 @@ if [ $BE -ne 0 ]; then fi new "start backend -s init -f $cfg" start_backend -s init -f $cfg - - new "wait backend" - wait_backend fi +new "wait backend" +wait_backend if [ $RC -ne 0 ]; then new "kill old restconf daemon" @@ -211,11 +210,11 @@ if [ $RC -ne 0 ]; then new "start restconf daemon" start_restconf -f $cfg - - new "wait restconf" - wait_restconf fi +new "wait restconf" +wait_restconf + # mandatory-leaf See RFC7950 Sec 7.17 new "netconf set interface with augmented type and mandatory leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO diff --git a/test/test_cli_auto.sh b/test/test_cli_auto.sh index 56f83606..27f52fdf 100755 --- a/test/test_cli_auto.sh +++ b/test/test_cli_auto.sh @@ -30,6 +30,7 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.pidfile $dir false + clixon-restconf EOF diff --git a/test/test_cli_auto_extension.sh b/test/test_cli_auto_extension.sh index 3afb12f5..08b0d62b 100755 --- a/test/test_cli_auto_extension.sh +++ b/test/test_cli_auto_extension.sh @@ -39,6 +39,7 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.pidfile $dir false + clixon-restconf EOF diff --git a/test/test_cli_auto_genmodel.sh b/test/test_cli_auto_genmodel.sh index d64ed9a3..ef886f98 100755 --- a/test/test_cli_auto_genmodel.sh +++ b/test/test_cli_auto_genmodel.sh @@ -41,6 +41,7 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.pidfile /usr/local/var/$APPNAME false + clixon-restconf EOF diff --git a/test/test_cli_auto_sub.sh b/test/test_cli_auto_sub.sh index bbfb9b1c..32ca8817 100755 --- a/test/test_cli_auto_sub.sh +++ b/test/test_cli_auto_sub.sh @@ -38,6 +38,7 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.pidfile $dir false + clixon-restconf EOF diff --git a/test/test_nacm_module_write.sh b/test/test_nacm_module_write.sh index df654c9f..12ed966c 100755 --- a/test/test_nacm_module_write.sh +++ b/test/test_nacm_module_write.sh @@ -186,7 +186,7 @@ nacm # replace all, then must include NACM rules as well # This usually triggers a 'HTTP/1.1 100 Continue' from curl as well MSG="$RULES" -new "update root list permit" +new "update root list permit (trigger 100 Continue)" expectpart "$(curl -u andy:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data -d "$MSG")" 0 'HTTP/1.1 204 No Content' new "delete root list deny" diff --git a/test/test_openconfig_interfaces.sh b/test/test_openconfig_interfaces.sh index e63b88b2..b7fde3e4 100755 --- a/test/test_openconfig_interfaces.sh +++ b/test/test_openconfig_interfaces.sh @@ -94,19 +94,19 @@ if [ $BE -ne 0 ]; then new "start backend -s startup -f $cfg" start_backend -s startup -f $cfg - - new "wait backend" - wait_backend fi +new "wait backend" +wait_backend + new "$clixon_cli -D $DBG -1f $cfg -y $f show version" expectpart "$($clixon_cli -D $DBG -1f $cfg show version)" 0 "${CLIXON_VERSION}" new "$clixon_netconf -qf $cfg" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^eeex:ethfalsetrue00truefalse]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^eeex:ethfalsetrue00]]>]]>" new "cli show configuration" -expectpart "$($clixon_cli -1 -f $cfg show conf xml)" 0 "^" "" +expectpart "$($clixon_cli -1 -f $cfg show conf xml)" 0 "^" --not-- "" new "cli set interfaces interface complete: e" expectpart "$(echo "set interfaces interface " | $clixon_cli -f $cfg)" 0 "interface e" diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index 49116d6b..5fd2cc5d 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -140,13 +140,13 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition derived-from(type, \"ex:ethernet\") of node mtu in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed on self)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition derived-from(type, \"ex:ethernet\") of node mtu in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -162,7 +162,7 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (crc not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition derived-from-or-self(type, \"ex:ethernet\") of node crc in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example']]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" diff --git a/test/test_yang_when.sh b/test/test_yang_when.sh new file mode 100755 index 00000000..884d9b30 --- /dev/null +++ b/test/test_yang_when.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +# Tests for yang uses/augment + when +# Uses+when, test: +# 1. set/get/validate matching and non-matching when statement +# 2 RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list, the +# "uses" statement MUST NOT have a "when" statement. +# XXX:Originally for cli tests but not specific to cli (clean when done?) + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +fin=$dir/in +cfg=$dir/conf_yang.xml +fyang=$dir/example.yang +clidir=$dir/cli +if [ -d $clidir ]; then + rm -rf $clidir/* +else + mkdir $clidir +fi + +# Use yang in example + +cat < $cfg + + $cfg + ietf-netconf:startup + /usr/local/share/clixon + $dir + $dir + /usr/local/lib/$APPNAME/backend + $clidir + /usr/local/lib/$APPNAME/cli + $APPNAME + 2 + VARS + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + false + clixon-restconf + +EOF + +cat < $clidir/ex.cli +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; + +# Autocli syntax tree operations +edit @datamodel, cli_auto_edit("datamodel"); +up, cli_auto_up("datamodel"); +top, cli_auto_top("datamodel"); +set @datamodel, cli_auto_set(); +merge @datamodel, cli_auto_merge(); +create @datamodel, cli_auto_create(); +delete("Delete a configuration item") { + @datamodel, cli_auto_del(); + all("Delete whole candidate configuration"), delete_all("candidate"); +} +show("Show a particular state of the system"){ + configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ + xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", false, false); +} +} +validate("Validate changes"), cli_validate(); +commit("Commit the changes"), cli_commit(); +quit("Quit"), cli_quit(); +EOF + + +function testrun() +{ + new "test params: -f $cfg" + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + + + fi + new "wait backend" + wait_backend + + # no match: name != kalle + new "netconf set non-match" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOpaul
merge
]]>]]>" "^]]>]]>$" + + new "netconf get default value not set" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^paul
]]>]]>$" + + new "netconf set non-match value expect fail" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOpaul42
merge
]]>]]>" "^applicationunknown-elementvalueerrorNode 'value' tagged with 'when' condition 'ex:name = 'kalle'' in module 'example' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)]]>]]>$" + + new "netconf get value without default" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^paul
]]>]]>$" + + new "netconf validate default not set" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + + new "netconf discard non-match" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + + # no match: name != kalle full put + new "netconf set non-match full" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOpaul42
merge
]]>]]>" "^applicationunknown-elementvalueerrorNode 'value' tagged with 'when' condition 'ex:name = 'kalle'' in module 'example' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)]]>]]>$" + + new "netconf get value empty" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + + # match: name = kalle + new "netconf set match" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOkalle
merge
]]>]]>" "^]]>]]>$" + + new "netconf get default value set" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^kallefoo
]]>]]>$" + + new "netconf set non-match value expect ok" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOkalle42
merge
]]>]]>" "^]]>]]>$" + + new "netconf get value ok" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^kalle42
]]>]]>$" + + new "netconf validate default set" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + + 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 +} + +cat < $fyang +module example { + namespace "urn:example:clixon"; + prefix ex; + + grouping value-top { + leaf value{ + type string; + default foo; + } + } + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + uses value-top { + when "ex:name = 'kalle'"; + } + } + } +} +EOF + +new "1. uses/when test edit,default" +testrun + +cat < $fyang +module example { + namespace "urn:example:clixon"; + prefix ex; + + grouping value-top { + leaf value{ + type string; + default foo; + } + } + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + + } + } + augment /table/parameter { + uses value-top { + when "ex:name = 'kalle'"; + } + } +} +EOF + +new "2. augment/uses/when test edit,default (same but with augment)" +testrun + +new "3. RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list" +# the "uses" statement MUST NOT have a "when" statement. + +cat < $fyang +module example { + namespace "urn:example:clixon"; + prefix ex; + + grouping value-top { + leaf name{ + type string; + } + } + container table{ + list parameter{ + key name; + uses value-top { + when "ex:name = 'kalle'"; + } + } + } +} +EOF + +if [ ${valgrindtest} -eq 0 ]; then # Error dont cleanup mem OK + new "start backend -s init -f $cfg, expect yang when fail" + expectpart "$(sudo $clixon_backend -F -D $DBG -s init -f $cfg 2>&1)" 255 "Yang error: Key leaf 'name' defined in grouping 'value-top' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5" +fi + +# kill backend +stop_backend -f $cfg + +rm -rf $dir + +new "endtest" +endtest