* YANG Leafref feature update

* Closer adherence to RFC 7950. Some of this is changed behavior, some is new feature.
  * Essentially instead of looking at the referring leaf, context is referred(target) node
  * Validation uses referred node
    * Validation changed to use type of referred node, instead of just "string"
  * Auto-cli
    * Changed to use type of referred node for typecheck
    * Completion uses referred node
  * Required instance / less strict validation
    * New: Leafrefs must refer to existing data leaf ONLY IF YANG `required-instance` is true
    * Previous: All leafrefs must refer to existing data leaf node
* Fixed: [Autocli does not offer completions for leafref to identityref #254](https://github.com/clicon/clixon/issues/254)
This commit is contained in:
Olof hagsand 2021-08-16 13:57:51 +02:00
parent 8db716ca07
commit 980718178a
18 changed files with 1151 additions and 115 deletions

View file

@ -35,6 +35,17 @@ Expected: September, 2021
### New features
* YANG Leafref feature update
* Closer adherence to RFC 7950. Some of this is changed behavior, some is new feature.
* Essentially instead of looking at the referring leaf, context is referred(target) node
* Validation uses referred node
* Validation changed to use type of referred node, instead of just "string"
* Auto-cli
* Changed to use type of referred node for typecheck
* Completion uses referred node
* Required instance / less strict validation
* New: Leafrefs must refer to existing data leaf ONLY IF YANG `required-instance` is true
* Previous: All leafrefs must refer to existing data leaf node
* Restconf YANG PATCH according to RFC 8072 (Work in progress)
* Experimental: enable by setting YANG_PATCH in include/clixon_custom.h
* Thanks to Alan Yaniger for providing this patch
@ -43,6 +54,9 @@ Expected: September, 2021
Users may have to change how they access the system
* See changes under new feature "YANG leafref feature update"
* Validation of referred node type (not referring)
* Leafref required-instance must be set to make strict data-node check
* Native Restconf is now default, not fcgi/nginx
* That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi`
* New clixon-config@2021-07-11.yang revision
@ -56,6 +70,8 @@ Users may have to change how they access the system
### Corrected Bugs
* Fixed: [Autocli does not offer completions for leafref to identityref #254](https://github.com/clicon/clixon/issues/254)
* This is a part of YANG Leafref feature update
* Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250)
* Fixed: Yang patterns: \n and other non-printable characters were broken
* Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10

View file

@ -106,14 +106,16 @@ You can see which CLISPEC it generates via clixon_cli -D 2:
* @param[in] options
* @param[in] fraction_digits
* @param[out] cb The string where the result format string is inserted.
* @retval 1 Hide, dont show helptext etc
* @retval 0 OK
* @retval -1 Error
* @see expand_dbvar This is where the expand string is used
* @note XXX only fraction_digits handled,should also have mincv, maxcv, pattern
*/
static int
cli_expand_var_generate(clicon_handle h,
yang_stmt *ys,
enum cv_type cvtype,
char *cvtypestr,
int options,
uint8_t fraction_digits,
cbuf *cb)
@ -129,8 +131,7 @@ cli_expand_var_generate(clicon_handle h,
}
if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
goto done;
cprintf(cb, "|<%s:%s", yang_argument_get(ys),
cv_type2str(cvtype));
cprintf(cb, "|<%s:%s", yang_argument_get(ys), cvtypestr);
if (options & YANG_OPTIONS_FRACTION_DIGITS)
cprintf(cb, " fraction-digits:%u", fraction_digits);
cprintf(cb, " %s(\"candidate\",\"%s\")>",
@ -495,12 +496,17 @@ yang2cli_var_union_one(clicon_handle h,
&ytype, &options, /* resolved type */
&cvv, patterns, NULL, &fraction_digits) < 0)
goto done;
if (ytype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
restype = ytype?yang_argument_get(ytype):NULL;
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
if (yang2cli_var_union(h, ys, origtype, ytype, helptext, cb) < 0)
goto done;
}
/* XXX leafref inside union ? */
else {
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done;
@ -554,9 +560,59 @@ yang2cli_var_union(clicon_handle h,
return retval;
}
static int
yang2cli_var_leafref(clicon_handle h,
yang_stmt *ys,
yang_stmt *yrestype,
char *helptext,
enum cv_type cvtype,
int options,
cvec *cvv,
cvec *patterns,
uint8_t fraction_digits,
cbuf *cb)
{
int retval = -1;
char *type;
int completionp;
char *cvtypestr;
int ret;
/* Give up: use yreferred
* XXX: inline of else clause below
*/
type = yrestype?yang_argument_get(yrestype):NULL;
cvtypestr = cv_type2str(cvtype);
if (type)
completionp = clicon_cli_genmodel_completion(h) &&
strcmp(type, "enumeration") != 0 &&
strcmp(type, "identityref") != 0 &&
strcmp(type, "bits") != 0;
else
completionp = clicon_cli_genmodel_completion(h);
if (completionp)
cprintf(cb, "(");
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
options, cvv, patterns, fraction_digits, cb) < 0)
goto done;
if (completionp){
if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
options, fraction_digits,
cb)) < 0)
goto done;
if (ret == 0)
yang2cli_helptext(cb, helptext);
cprintf(cb, ")");
}
retval = 0;
done:
return retval;
}
/*! Generate CLI code for Yang leaf statement to CLIgen variable
* @param[in] h Clixon handle
* @param[in] ys Yang statement
* @param[in] ys Yang statement of original leaf
* @param[in] ys Yang statement of referred node for type (leafref)
* @param[in] helptext CLI help text
* @param[out] cb Buffer where cligen code is written
@ -566,10 +622,15 @@ yang2cli_var_union(clicon_handle h,
* sub-types.
* eg type union{ type int32; type string } --> (<x:int32>| <x:string>)
* Another is multiple ranges
* @note leafrefs are troublesome. In this code their cligen type are string, but they should really
* be the type of the referred node. But since the path pointing to the referred node is XML, and
* only YANG is known here, we cannot easily determine the YANG node of the referred XML node,
* and thus its type.
*/
static int
yang2cli_var(clicon_handle h,
yang_stmt *ys,
yang_stmt *yreferred,
char *helptext,
cbuf *cb)
{
@ -581,67 +642,79 @@ yang2cli_var(clicon_handle h,
cvec *patterns = NULL;
uint8_t fraction_digits = 0;
enum cv_type cvtype;
char *cvtypestr;
int options = 0;
int completionp, result;
char *type;
int result;
if ((patterns = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
if (yang_type_get(ys, &origtype, &yrestype,
if (yang_type_get(yreferred, &origtype, &yrestype,
&options, &cvv, patterns, NULL, &fraction_digits) < 0)
goto done;
restype = yrestype?yang_argument_get(yrestype):NULL;
restype = yang_argument_get(yrestype);
if (restype && strcmp(restype, "empty") == 0){
retval = 0;
goto done;
}
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
if (strcmp(restype, "empty") == 0)
goto ok;
if (clicon_type2cv(origtype, restype, yreferred, &cvtype) < 0)
goto done;
cvtypestr = cv_type2str(cvtype);
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
if (restype && strcmp(restype, "union") == 0){
if (strcmp(restype, "union") == 0){
/* Union: loop over resolved type's sub-types (can also be recursive unions) */
cprintf(cb, "(");
if (yang2cli_var_union(h, ys, origtype, yrestype, helptext, cb) < 0)
goto done;
if (clicon_cli_genmodel_completion(h)){
result = cli_expand_var_generate(h, ys, cvtype,
options, fraction_digits,
cb);
if (result < 0)
if ((result = cli_expand_var_generate(h, ys, cvtypestr,
options, fraction_digits,cb)) < 0)
goto done;
if (result == 0)
yang2cli_helptext(cb, helptext);
if (result == 0)
yang2cli_helptext(cb, helptext);
}
cprintf(cb, ")");
}
else{
type = yrestype?yang_argument_get(yrestype):NULL;
if (type)
completionp = clicon_cli_genmodel_completion(h) &&
strcmp(type, "enumeration") != 0 &&
strcmp(type, "identityref") != 0 &&
strcmp(type, "bits") != 0;
else
completionp = clicon_cli_genmodel_completion(h);
if (completionp)
cprintf(cb, "(");
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
options, cvv, patterns, fraction_digits, cb) < 0)
else if (strcmp(restype,"leafref")==0){
yang_stmt *ypath;
char *path_arg;
yang_stmt *yref = NULL;
if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){
clicon_err(OE_YANG, 0, "No Y_PATH for leafref");
goto done;
if (completionp){
result = cli_expand_var_generate(h, ys, cvtype,
options, fraction_digits,
cb);
if (result < 0)
goto done;
if (result == 0)
yang2cli_helptext(cb, helptext);
cprintf(cb, ")");
}
if ((path_arg = yang_argument_get(ypath)) == NULL){
clicon_err(OE_YANG, 0, "No argument for Y_PATH");
goto done;
}
if (yang_path_arg(yreferred, path_arg, &yref) < 0)
goto done;
if (yref == NULL){
/* Give up: use yreferred
*/
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
cvv, patterns, fraction_digits, cb) < 0)
goto done;
}
else {
if (yreferred == yref){
clicon_err(OE_YANG, 0, "Referred YANG node for leafref path %s points to self", path_arg);
goto done;
}
/* recurse call with new referred node */
if (yang2cli_var(h, ys, yref, helptext, cb) < 0)
goto done;
}
}
else{
if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options,
cvv, patterns, fraction_digits, cb) < 0)
goto done;
}
ok:
retval = 0;
done:
if (origtype)
@ -704,7 +777,7 @@ yang2cli_leaf(clicon_handle h,
cprintf(cb, ",hide-database-auto-completion{");
extralevel = 1;
}
if (yang2cli_var(h, ys, helptext, cb) < 0)
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
goto done;
}
else{
@ -718,7 +791,7 @@ yang2cli_leaf(clicon_handle h,
}
else{
if (!show_tree || key_leaf) {
if (yang2cli_var(h, ys, helptext, cb) < 0)
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
goto done;
}
}

View file

@ -73,6 +73,7 @@
#include "cli_common.h" /* internal functions */
/*! Given an xpath encoded in a cbuf, append a second xpath into the first
*
* The method reuses prefixes from xpath1 if they exist, otherwise the module prefix
* from y is used. Unless the element is .., .
* XXX: Predicates not handled
@ -87,10 +88,10 @@ and
traverse_canonical
*/
static int
xpath_myappend(cbuf *xpath0,
char *xpath1,
yang_stmt *y,
cvec *nsc)
xpath_append(cbuf *cb0,
char *xpath1,
yang_stmt *y,
cvec *nsc)
{
int retval = -1;
char **vec = NULL;
@ -100,27 +101,50 @@ xpath_myappend(cbuf *xpath0,
char *myprefix;
char *id = NULL;
char *prefix = NULL;
int initialups = 1; /* If starts with ../../.. */
char *xpath0;
if (xpath0 == NULL){
clicon_err(OE_XML, EINVAL, "xpath0 is NULL");
if (cb0 == NULL){
clicon_err(OE_XML, EINVAL, "cb0 is NULL");
goto done;
}
if (xpath1 == NULL || strlen(xpath1)==0)
goto ok;
if ((myprefix = yang_find_myprefix(y)) == NULL)
goto done;
if ((vec = clicon_strsep(xpath1, "/", &nvec)) == NULL)
goto done;
if (xpath1 && xpath1[0] == '/')
cbuf_reset(xpath0);
if (xpath1[0] == '/')
cbuf_reset(cb0);
xpath0 = cbuf_get(cb0);
for (i=0; i<nvec; i++){
v = vec[i];
if (strlen(v) == 0)
continue;
if (nodeid_split(v, &prefix, &id) < 0)
goto done;
if (strcmp(id, "..") == 0 || strcmp(id, ".") == 0)
cprintf(xpath0, "/%s", id);
else
cprintf(xpath0, "/%s:%s", prefix?prefix:myprefix, id);
if (strcmp(id, ".") == 0)
initialups = 0;
else if (strcmp(id, "..") == 0){
if (initialups){
/* Subtract from xpath0 */
int j;
for (j=cbuf_len(cb0); j >= 0; j--){
if (xpath0[j] != '/')
continue;
cbuf_trunc(cb0, j);
break;
}
}
else{
initialups = 0;
cprintf(cb0, "/%s", id);
}
}
else{
initialups = 0;
cprintf(cb0, "/%s:%s", prefix?prefix:myprefix, id);
}
if (prefix){
free(prefix);
prefix = NULL;
@ -130,6 +154,7 @@ xpath_myappend(cbuf *xpath0,
id = NULL;
}
}
ok:
retval = 0;
done:
if (prefix)
@ -182,13 +207,13 @@ expand_dbvar(void *h,
cxobj *xbot = NULL; /* xpath, NULL if datastore */
yang_stmt *y = NULL; /* yang spec of xpath */
yang_stmt *yp;
yang_stmt *ytype;
yang_stmt *ypath;
char *reason = NULL;
cvec *nsc = NULL;
int ret;
int cvvi = 0;
cbuf *cbxpath = NULL;
yang_stmt *ypath;
yang_stmt *ytype;
if (argv == NULL || cvec_len(argv) != 2){
clicon_err(OE_PLUGIN, EINVAL, "requires arguments: <db> <xmlkeyfmt>");
@ -238,7 +263,6 @@ expand_dbvar(void *h,
}
if (y==NULL)
goto ok;
/* Transform api-path to xpath for netconf */
if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0)
goto done;
@ -287,7 +311,7 @@ expand_dbvar(void *h,
/* */
/* Extend xpath with leafref path: Append yang_argument_get(ypath) to xpath
*/
if (xpath_myappend(cbxpath, yang_argument_get(ypath), y, nsc) < 0)
if (xpath_append(cbxpath, yang_argument_get(ypath), y, nsc) < 0)
goto done;
}
/* Get configuration based on cbxpath */
@ -297,7 +321,7 @@ expand_dbvar(void *h,
clixon_netconf_error(xe, "Get configuration", NULL);
goto ok;
}
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, cbuf_get(cbxpath)) < 0)
goto done;
/* Loop for inserting into commands cvec.
* Detect duplicates: for ordered-by system assume list is ordered, so you need

View file

@ -101,6 +101,7 @@ extern "C" {
#include <clixon/clixon_xpath_ctx.h>
#include <clixon/clixon_xpath.h>
#include <clixon/clixon_xpath_optimize.h>
#include <clixon/clixon_xpath_yang.h>
#include <clixon/clixon_json.h>
#include <clixon/clixon_nacm.h>
#include <clixon/clixon_xml_changelog.h>

View file

@ -0,0 +1,47 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC (Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10
* Note: for YANG which is constrained to path-arg as defined in rfc7950
* See: clixon_xpath.[ch] for full XML XPATH implementation
*/
#ifndef _CLIXON_XPATH_YANG_H
#define _CLIXON_XPATH_YANG_H
/*
* Prototypes
*/
int yang_path_arg(yang_stmt *ys, const char *xpath, yang_stmt **yref);
#endif /* _CLIXON_XPATH_YANG_H */

View file

@ -80,7 +80,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
clixon_path.c clixon_validate.c \
clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.c \
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c clixon_xpath_optimize.c \
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \
clixon_xpath_optimize.c clixon_xpath_yang.c \
clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c

View file

@ -88,7 +88,21 @@
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* From rfc7950 Sec 9.9.2
* From rfc7950
* Sec 9.9:
* The leafref built-in type is restricted to the value space of some
* leaf or leaf-list node in the schema tree and optionally further
* restricted by corresponding instance nodes in the data tree. The
* "path" substatement (Section 9.9.2) is used to identify the referred
* leaf or leaf-list node in the schema tree. The value space of the
* referring node is the value space of the referred node.
*
* If the "require-instance" property (Section 9.9.3) is "true", there
* MUST exist a node in the data tree, or a node with a default value in
* use (see Sections 7.6.1 and 7.7.2), of the referred schema tree leaf
* or leaf-list node with the same value as the leafref value in a valid
* data tree.
* Sec 9.9.2:
* The "path" XPath expression is evaluated in the following context,
* in addition to the definition in Section 6.4.1:
* o If the "path" statement is defined within a typedef, the context
@ -96,6 +110,7 @@
* references the typedef. (ie ys)
* o Otherwise, the context node is the node in the data tree for which
* the "path" statement is defined. (ie yc)
*
*/
static int
validate_leafref(cxobj *xt,
@ -105,6 +120,7 @@ validate_leafref(cxobj *xt,
{
int retval = -1;
yang_stmt *ypath;
yang_stmt *yreqi;
cxobj **xvec = NULL;
cxobj *x;
int i;
@ -113,10 +129,18 @@ validate_leafref(cxobj *xt,
char *leafbody;
cvec *nsc = NULL;
cbuf *cberr = NULL;
char *path;
char *path_arg;
yang_stmt *ymod;
cg_var *cv;
int require_instance = 0;
if ((leafrefbody = xml_body(xt)) == NULL)
/* require instance */
if ((yreqi = yang_find(ytype, Y_REQUIRE_INSTANCE, NULL)) != NULL){
if ((cv = yang_cv_get(yreqi)) != NULL) /* shouldnt happen */
require_instance = cv_bool_get(cv);
}
/* Find referred XML instances */
if (require_instance == 0)
goto ok;
if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
if ((cberr = cbuf_new()) == NULL){
@ -130,11 +154,16 @@ validate_leafref(cxobj *xt,
goto done;
goto fail;
}
if ((path_arg = yang_argument_get(ypath)) == NULL){
clicon_err(OE_YANG, 0, "No argument for Y_PATH");
goto done;
}
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
/* See comment^: If path is defined in typedef or not */
if (xml_nsctx_node(xt, &nsc) < 0)
goto done;
path = yang_argument_get(ypath);
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path) < 0)
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path_arg) < 0)
goto done;
for (i = 0; i < xlen; i++) {
x = xvec[i];
@ -151,7 +180,7 @@ validate_leafref(cxobj *xt,
ymod = ys_module(ys);
cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d",
leafrefbody,
path,
path_arg,
yang_argument_get(ymod),
yang_linenum_get(ys));
if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0)
@ -1029,8 +1058,9 @@ xml_yang_validate_add(clicon_handle h,
clicon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
/* In the union case, value is parsed as generic REST type,
/* In the union and leafref case, value is parsed as generic REST type,
* needs to be reparsed when concrete type is selected
* see ys_cv_validate_union_one and ys_cv_validate_leafref
*/
if ((body = xml_body(xt)) == NULL){
/* We do not allow ints to be empty. Otherwise NULL strings

View file

@ -2154,6 +2154,10 @@ yang_enum_int_value(cxobj *node,
if (yang_type_resolve(ys, ys, ytype, &yrestype,
NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
if (yrestype==NULL || strcmp(yang_argument_get(yrestype), "enumeration"))
goto done;
if ((yenum = yang_find(yrestype, Y_ENUM, xml_body(node))) == NULL)
@ -2327,3 +2331,4 @@ yang_check_when_xpath(cxobj *xn,
xml_nsctx_free(nsc);
return retval;
}

View file

@ -90,7 +90,7 @@ ctx_free(xp_ctx *xc)
xp_ctx *
ctx_dup(xp_ctx *xc0)
{
static xp_ctx *xc = NULL;
xp_ctx *xc = NULL;
if ((xc = malloc(sizeof(*xc))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");

View file

@ -480,15 +480,15 @@ xp_eval_predicate(xp_ctx *xc,
cxobj *x;
xp_ctx *xcc;
if (xs->xs_c0 == NULL){ /* empty */
if ((xr0 = ctx_dup(xc)) == NULL)
goto done;
}
else{ /* eval previous predicates */
if (xs->xs_c0 != NULL){ /* eval previous predicates */
if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0)
goto done;
}
if (xs->xs_c1){
else{ /* empty */
if ((xr0 = ctx_dup(xc)) == NULL)
goto done;
}
if (xs->xs_c1){ /* Second child */
/* Loop over each node in the nodeset */
assert (xr0->xc_type == XT_NODESET);
if ((xr1 = malloc(sizeof(*xr1))) == NULL){
@ -537,16 +537,18 @@ xp_eval_predicate(xp_ctx *xc,
ctx_free(xrc);
}
}
assert(xr0||xr1);
if (xr0 == NULL && xr1 == NULL){
clicon_err(OE_XML, EFAULT, "Internal error: no result produced");
goto done;
}
if (xr1){
*xrp = xr1;
xr1 = NULL;
}
else
if (xr0){
*xrp = xr0;
xr0 = NULL;
}
else if (xr0){
*xrp = xr0;
xr0 = NULL;
}
retval = 0;
done:
if (xr0)
@ -1005,12 +1007,11 @@ xp_eval(xp_ctx *xc,
/* // is short for /descendant-or-self::node()/ */
if (xs->xs_int == A_DESCENDANT_OR_SELF)
xc->xc_descendant = 1; /* XXX need to set to 0 in sub */
break;
case XP_STEP: /* XP_NODE is first argument -not called explicitly */
if (xp_eval_step(xc, xs, nsc, localonly, xrp) < 0)
goto done;
goto ok;
goto ok; /* Skip generic child traverse */
break;
case XP_PRED:
if (xp_eval_predicate(xc, xs, nsc, localonly, xrp) < 0)
@ -1167,12 +1168,11 @@ xp_eval(xp_ctx *xc,
/* Eval second child c1
* Note, some operators like locationpath, need transitive context (use_xr0)
*/
if (xs->xs_c1)
if (xs->xs_c1){
if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, localonly, &xr1) < 0)
goto done;
/* Actions after second child
*/
if (xs->xs_c1)
/* Actions after second child
*/
switch (xs->xs_type){
case XP_AND: /* combine and and or ops */
if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0)
@ -1192,15 +1192,12 @@ xp_eval(xp_ctx *xc,
default:
break;
}
}
xc->xc_descendant = 0;
#if 0
assert(xr0||xr1||xr2); /* for debugging */
#else
if (xr0 == NULL && xr1 == NULL && xr2 == NULL){
clicon_err(OE_XML, EFAULT, "Internal error: no result produced");
goto done;
}
#endif
if (xr2){
*xrp = xr2;
xr2 = NULL;

463
lib/src/clixon_xpath_yang.c Normal file
View file

@ -0,0 +1,463 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2, indicate
your decision by deleting the provisions above and replace them with the
notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10
* Note: for YANG which is constrained to path-arg as defined in rfc7950
* See: clixon_xpath.[ch] for full XML XPATH implementation
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_yang_module.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xpath_function.h"
#include "clixon_xpath_yang.h"
/* Assume single yang node context */
struct xp_yang_ctx{
enum xp_objtype xy_type;
yang_stmt *xy_node; /* If type is XT_NODESET */
int xy_bool; /* If type is XT_BOOL */
yang_stmt *xy_initial; /* RFC 7960 10.1.1 extension: for current() */
};
typedef struct xp_yang_ctx xp_yang_ctx;
/* Forward */
static int xp_yang_eval(xp_yang_ctx *xy, xpath_tree *xptree, xp_yang_ctx **xyr);
/*! Duplicate xpath yang context */
xp_yang_ctx *
xy_dup(xp_yang_ctx *xy0)
{
xp_yang_ctx *xy = NULL;
if ((xy = malloc(sizeof(*xy))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(xy, 0, sizeof(*xy));
if (xy0)
*xy = *xy0;
else
xy->xy_type = XT_NODESET;
done:
return xy;
}
/*! xpath yang equality operator returns true
*
* rfc7950 sec 9.9.2:
* Predicates are used only for constraining the values for the
* key nodes for list entries. Each predicate consists of exactly one
* equality test per key
* Always evaluates to true since there are no instances
*/
static int
xp_yang_op_eq(xp_yang_ctx *xy1,
xp_yang_ctx *xy2,
xp_yang_ctx **xyr)
{
int retval = -1;
xp_yang_ctx *xy = NULL;
if ((xy = xy_dup(xy1)) == NULL)
goto done;
if(xy1 == NULL || xy2 == NULL || xy1->xy_node == NULL || xy2->xy_node == NULL){
clicon_err(OE_YANG, EINVAL, "Error in xy1 or xy2 ");
goto done;
}
xy->xy_type = XT_BOOL;
xy->xy_bool = 1;
xy->xy_node = NULL;
*xyr = xy;
retval = 0;
done:
return retval;
}
/*! Evaluate leafref PATH-ARG step rule on a YANG tree
*
* @param[in] xy0 Incoming context
* @param[in] xpath_tree XPATH parse-tree
* @param[out] xyr Resulting context
* @retval 0 OK
* @retval -1 Error
* @see xp_eval_step
*/
static int
xp_yang_eval_step(xp_yang_ctx *xy0,
xpath_tree *xptree,
xp_yang_ctx **xyr)
{
int retval = -1;
xpath_tree *nodetest; /* needed if child */
char *prefix;
yang_stmt *ys;
xp_yang_ctx *xy = NULL;
/* Create new xy */
if ((xy = xy_dup(xy0)) == NULL)
goto done;
ys = xy->xy_node;
switch (xptree->xs_int){
case A_CHILD:
if ((nodetest = xptree->xs_c0) == NULL){
clicon_err(OE_YANG, 0, "child step nodetest expected");
goto done;
}
switch (nodetest->xs_type){
case XP_NODE:
if ((prefix = nodetest->xs_s0) != NULL){
if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */
yang_stmt *ys1 = NULL;
/* XXX: Kludge with prefixes */
if ((ys1 = yang_find_module_by_prefix(ys, prefix)) == NULL)
ys1 = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix);
if (ys1 != NULL)
ys = ys1;
}
}
xy->xy_node = yang_find_schemanode(ys, nodetest->xs_s1);
if (xy->xy_node == NULL){
*xyr = xy;
xy = NULL;
goto ok;
}
break;
case XP_NODE_FN:
break;
default:
clicon_err(OE_YANG, 0, "Invalid xpath-tree nodetest: %s",
xpath_tree_int2str(nodetest->xs_type));
goto done;
break;
} /* nodetest xs_type */
break;
case A_PARENT:
xy->xy_node = yang_parent_get(ys);
break;
default:
clicon_err(OE_YANG, 0, "Invalid path-arg step: %s",
axis_type_int2str(xptree->xs_int));
goto done;
break;
}
if (xptree->xs_c1){
if (xp_yang_eval(xy, xptree->xs_c1, xyr) < 0)
goto done;
}
else{
*xyr = xy;
xy = NULL;
}
ok:
retval = 0;
done:
if (xy)
free(xy);
return retval;
}
/*! Evaluate leafref PATH-ARG predicate rule on a YANG tree
*
* @param[in] xy Incoming context
* @param[in] xpath_tree XPATH parse-tree
* @param[out] xyr Resulting context
* @retval 0 OK
* @retval -1 Error
* @see xp_eval_predicate
*/
static int
xp_yang_eval_predicate(xp_yang_ctx *xy,
xpath_tree *xptree,
xp_yang_ctx **xyr)
{
int retval = -1;
xp_yang_ctx *xy0 = NULL;
xp_yang_ctx *xy1 = NULL;
if (xptree->xs_c0 != NULL){ /* eval previous predicates */
if (xp_yang_eval(xy, xptree->xs_c0, &xy0) < 0)
goto done;
}
else{ /* empty */
if ((xy0 = xy_dup(xy)) == NULL)
goto done;
}
if (xptree->xs_c1){ /* Second child */
// if ((xy1 = xy_dup(xy)) == NULL)
// goto done;
/* the PredicateExpr is evaluated with the node as the context node */
if (xp_yang_eval(xy0, xptree->xs_c1, &xy1) < 0)
goto done;
/* Check xrc: if "true" then xyr=xy0? */
if (xy1->xy_type == XT_BOOL && xy1->xy_bool)
;
else
xy0->xy_node = NULL;
}
*xyr = xy0;
xy0 = NULL;
retval = 0;
done:
if (xy0)
free(xy0);
if (xy1)
free(xy1);
return retval;
}
/*! Evaluate leafref PATH-ARG on a YANG tree
*
* @param[in] xy Incoming context
* @param[in] xpath_tree XPATH parse-tree
* @param[out] xyr Resulting context
* @retval 0 OK
* @retval -1 Error
* @see xp_eval
*/
static int
xp_yang_eval(xp_yang_ctx *xy,
xpath_tree *xptree,
xp_yang_ctx **xyr)
{
int retval = -1;
int use_xy0 = 0;
xp_yang_ctx *xy0 = NULL;
xp_yang_ctx *xy1 = NULL;
xp_yang_ctx *xy2 = NULL;
/* If empty npodeset, quit, cannot continue */
if (xy->xy_type == XT_NODESET && xy->xy_node == NULL)
goto ok;
/* Pre-actions before check first child c0
*/
switch (xptree->xs_type){
case XP_EXP:
case XP_AND:
case XP_ADD:
case XP_UNION:
if (xptree->xs_c1 != NULL){
clicon_err(OE_XML, 0, "Function %s having two args is invalid for path-arg", xptree->xs_s0);
goto done;
}
break;
case XP_RELEX:
case XP_PATHEXPR:
case XP_FILTEREXPR:
break;
case XP_LOCPATH:
case XP_NODE:
case XP_NODE_FN:
break;
case XP_RELLOCPATH:
break;
case XP_PRIME_FN:
if (xptree->xs_s0){
switch (xptree->xs_int){
case XPATHFN_CURRENT:
if ((*xyr = xy_dup(xy)) == NULL)
goto done;
(*xyr)->xy_node = (*xyr)->xy_initial;
goto ok;
break;
default:
clicon_err(OE_XML, 0, "Function %s invalid for path-arg", xptree->xs_s0);
goto done;
}
}
break;
case XP_ABSPATH:
/* Set context node to top node, and nodeset to that node only */
xy->xy_node = ys_module(xy->xy_node);
break;
case XP_PRED:
if (xp_yang_eval_predicate(xy, xptree, xyr) < 0)
goto done;
goto ok; /* Skip generic child traverse */
break;
case XP_STEP: /* XP_NODE is first argument -not called explicitly */
if (xp_yang_eval_step(xy, xptree, xyr) < 0)
goto done;
goto ok; /* Skip generic child traverse */
break;
default: /* Here we explicitly fail on node types for those not appearing in path-arg */
clicon_err(OE_YANG, 0, "Invalid xpath-tree node name: %s",
xpath_tree_int2str(xptree->xs_type));
goto done;
break;
}
/* Eval first child c0
*/
if (xptree->xs_c0){
if (xp_yang_eval(xy, xptree->xs_c0, &xy0) < 0)
goto done;
}
/* Actions between first and second child
*/
switch (xptree->xs_type){
case XP_RELLOCPATH:
case XP_ABSPATH:
use_xy0++;
break;
case XP_PATHEXPR:
if (xptree->xs_c1)
use_xy0++;
break;
default:
break;
}
/* Eval second child c1
* Note, some operators like locationpath, need transitive context (use_xr0)
*/
if (xptree->xs_c1){
if (xp_yang_eval(use_xy0?xy0:xy, xptree->xs_c1, &xy1) < 0)
goto done;
/* Actions after second child
*/
switch (xptree->xs_type){
case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */
/* Check op: only EQ allowed in path-arg */
if (xptree->xs_int != XO_EQ){
clicon_err(OE_YANG, 0, "Invalid xpath-tree relational operator: %d, only eq allowed",
xptree->xs_int);
goto done;
}
if (xp_yang_op_eq(xy0, xy1, &xy2) < 0)
goto done;
break;
default:
break;
}
}
if (xy0 == NULL && xy1 == NULL && xy2 == NULL){
clicon_err(OE_XML, EFAULT, "Internal error: no result produced");
goto done;
}
if (xy2){
*xyr = xy2;
xy2 = NULL;
}
else if (xy1){
*xyr = xy1;
xy1 = NULL;
}
else if (xy0){
*xyr = xy0;
xy0 = NULL;
}
ok:
retval = 0;
done:
if (xy2)
free(xy2);
if (xy1)
free(xy1);
if (xy0)
free(xy0);
return retval;
}
/*! Resolve a yang node given a start yang node and a leafref path-arg
*
* Leafrefs have a path arguments that are used both for finding referred XML node instances as well
* as finding a referred YANG node for typechecks.
* Such a path-arg is defined as:
* The syntax for a path argument is a subset of the XPath abbreviated
* syntax. Predicates are used only for constraining the values for the
* key nodes for list entries. Each predicate consists of exactly one
* equality test per key, and multiple adjacent predicates MAY be
* present if a list has multiple keys.
* @param[in] ys YANG referring leaf node
* @param[in] path_arg Leafref path-arg
* @param[out] yref YANG referred node
* @note this function uses XPATH parser, which is (much too) general
* @see rfc7950 Sec 9.9.2
* @see rfc7950 Sec 14 (leafref path)
*/
int
yang_path_arg(yang_stmt *ys,
const char *path_arg,
yang_stmt **yref)
{
int retval = -1;
xpath_tree *xptree = NULL;
xp_yang_ctx *xyr = NULL;
xp_yang_ctx *xy = NULL;
if (xpath_parse(path_arg, &xptree) < 0)
goto done;
if ((xy = xy_dup(NULL)) == NULL)
goto done;
xy->xy_node = ys;
xy->xy_initial = ys;
if (xp_yang_eval(xy, xptree, &xyr) < 0)
goto done;
if (xyr != NULL)
*yref = xyr->xy_node;
retval = 0;
done:
if (xptree)
xpath_tree_free(xptree);
if (xyr)
free(xyr);
if (xy)
free(xy);
return retval;
}

View file

@ -2054,6 +2054,10 @@ ys_populate_range(clicon_handle h,
if (yang_type_resolve(ys, ys, (yang_stmt*)yparent, &yrestype,
&options, NULL, NULL, NULL, &fraction_digits) < 0)
goto done;
if (yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
restype = yrestype?yrestype->ys_argument:NULL;
if (nodeid_split(yang_argument_get(yparent), NULL, &origtype) < 0)
goto done;
@ -2540,6 +2544,7 @@ ys_populate2(yang_stmt *ys,
break;
case Y_MANDATORY: /* call yang_mandatory() to check if set */
case Y_CONFIG:
case Y_REQUIRE_INSTANCE:
if (ys_parse(ys, CGV_BOOL) == NULL)
goto done;
break;

View file

@ -80,6 +80,7 @@ struct yang_stmt{
leaf-list,
config: boolean true or false
mandatory: boolean true or false
require-instance: true or false
fraction-digits for fraction-digits
unknown-stmt (optional argument)
*/

View file

@ -96,6 +96,9 @@
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xpath_yang.h"
#include "clixon_yang_module.h"
#include "clixon_plugin.h"
#include "clixon_options.h"
@ -122,7 +125,7 @@ static const map_str2int ytmap[] = {
{"int8", CGV_INT8},
{"int16", CGV_INT16},
{"int64", CGV_INT64},
{"leafref", CGV_STRING}, /* XXX */
{"leafref", CGV_REST}, /* Is replaced by actual type */
{"uint8", CGV_UINT8},
{"uint16", CGV_UINT16},
{"uint32", CGV_UINT32},
@ -148,7 +151,7 @@ static const map_str2int ytmap2[] = {
{"int32", CGV_INT32},
{"int64", CGV_INT64},
{"int8", CGV_INT8},
{"leafref", CGV_STRING}, /* XXX */
{"leafref", CGV_REST}, /* Is replaced by actual type */
{"string", CGV_STRING},
{"uint16", CGV_UINT16},
{"uint32", CGV_UINT32},
@ -250,7 +253,10 @@ ys_resolve_type(yang_stmt *ys,
ys, &resolved,
&options, &cvv, patterns, NULL, &fraction) < 0)
goto done;
if (resolved == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
/* Cache the type resolving locally. Only place where this is done.
* Why not do it in yang_type_resolve? (compile regexps needs clicon_handle)
*/
@ -341,20 +347,12 @@ clicon_type2cv(char *origtype,
yang_stmt *ys,
enum cv_type *cvtype)
{
int retval = -1;
int retval = -1;
yang_stmt *ym;
*cvtype = CGV_ERR;
ym = ys_module(ys);
if (restype != NULL){
yang2cv_type(restype, cvtype);
if (*cvtype == CGV_ERR){
clicon_err(OE_YANG, 0, "%s: \"%s\" type not translated",
yang_argument_get(ym), restype);
goto done;
}
}
else{
if (restype == NULL){
/*
* Not resolved, but we can use special cligen types, eg ipv4addr
* Note this is a kludge or at least if we intend of using rfc types
@ -366,6 +364,15 @@ clicon_type2cv(char *origtype,
goto done;
}
}
else {
yang2cv_type(restype, cvtype);
if (*cvtype == CGV_ERR){
clicon_err(OE_YANG, 0, "%s: \"%s\" type not translated",
yang_argument_get(ym), restype);
goto done;
}
}
retval = 0;
done:
return retval;
@ -742,6 +749,57 @@ cv_validate1(clicon_handle h,
static int ys_cv_validate_union(clicon_handle h,yang_stmt *ys, char **reason,
yang_stmt *yrestype, char *type, char *val, yang_stmt **ysubp);
static int
ys_cv_validate_leafref(clicon_handle h,
char *body,
yang_stmt *ys,
yang_stmt *yrestype,
yang_stmt **ysub,
char **reason)
{
int retval = -1;
yang_stmt *yref = NULL;
char *path_arg;
yang_stmt *ypath;
cg_var *cv = NULL;
int ret;
if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){
clicon_err(OE_YANG, 0, "No Y_PATH for leafref");
goto done;
}
if ((path_arg = yang_argument_get(ypath)) == NULL){
clicon_err(OE_YANG, 0, "No argument for Y_PATH");
goto done;
}
if (yang_path_arg(ys, path_arg, &yref) < 0)
goto done;
if (yref == NULL){
clicon_err(OE_YANG, 0, "No referred YANG node found for leafref path %s", path_arg);
goto done;
}
/* reparse cv with new type */
if ((cv = cv_dup(yang_cv_get(yref))) == NULL){
clicon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
if ((ret = cv_parse1(body, cv, reason)) < 0){
clicon_err(OE_UNIX, errno, "cv_parse");
goto done;
}
if (ret == 0)
goto fail;
/* Recursive call to this function, but using refererred YANG node */
retval = ys_cv_validate(h, cv, yref, ysub, reason);
done:
if (cv)
cv_free(cv);
return retval;
fail:
retval = 0;
goto done;
}
/*!
* @param[in] h Clixon handle
* @param[in] ys Yang statement (union)
@ -762,7 +820,7 @@ ys_cv_validate_union_one(clicon_handle h,
char *val)
{
int retval = -1;
yang_stmt *yrt; /* union subtype */
yang_stmt *yrestype; /* union subtype */
int options = 0;
cvec *cvv = NULL;
cvec *regexps = NULL;
@ -781,12 +839,21 @@ ys_cv_validate_union_one(clicon_handle h,
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
if (yang_type_resolve(ys, ys, yt, &yrt, &options, &cvv, patterns, regexps,
if (yang_type_resolve(ys, ys, yt, &yrestype, &options, &cvv, patterns, regexps,
&fraction) < 0)
goto done;
restype = yrt?yang_argument_get(yrt):NULL;
if (yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
restype = yrestype?yang_argument_get(yrestype):NULL;
if (restype && strcmp(restype, "union") == 0){ /* recursive union */
if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val, &ysubt)) < 0)
if ((retval = ys_cv_validate_union(h, ys, reason, yrestype, type, val, &ysubt)) < 0)
goto done;
}
/* Leafref needs to resolve referred node for type information */
else if (restype && strcmp(restype,"leafref") == 0){
if ((retval = ys_cv_validate_leafref(h, val, ys, yrestype, NULL, reason)) < 0) /* XXX: ysub? */
goto done;
}
else {
@ -821,7 +888,7 @@ ys_cv_validate_union_one(clicon_handle h,
goto done;
}
if ((retval = cv_validate1(h, cvt, cvtype, options, cvv,
regexps, yrt, restype, reason)) < 0)
regexps, yrestype, restype, reason)) < 0)
goto done;
}
done:
@ -963,7 +1030,10 @@ ys_cv_validate(clicon_handle h,
}
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
if (restype && strcmp(restype, "union") == 0){
assert(cvtype == CGV_REST);
if (cvtype != CGV_REST){
clicon_err(OE_YANG, 0, "union must be rest cv type but is %d", cvtype);
goto done;
}
/* Instead of NULL, give an empty string to validate, this is to avoid cv_parse
* errors and may actually be the wrong thing to do.
*/
@ -987,6 +1057,28 @@ ys_cv_validate(clicon_handle h,
regexps) < 0)
goto done;
}
/* Leafref needs to resolve referred node for type information
* From rfc7950 Sec 9.9:
* The leafref built-in type is restricted to the value space of some
* leaf or leaf-list node in the schema tree and optionally further
* restricted by corresponding instance nodes in the data tree. The
* "path" substatement (Section 9.9.2) is used to identify the referred
* leaf or leaf-list node in the schema tree. The value space of the
* referring node is the value space of the referred node.
*/
if (restype && strcmp(restype,"leafref") == 0){
if (cvtype != CGV_REST){
clicon_err(OE_YANG, 0, "leafref must be rest cv type but is %d", cvtype);
goto done;
}
/* Instead of NULL, give an empty string to validate, this is to avoid cv_parse
* errors and may actually be the wrong thing to do.
*/
if ((val = cv_string_get(cv)) == NULL)
val = "";
retval = ys_cv_validate_leafref(h, val, ys, yrestype, ysub, reason);
goto done;
}
if ((retval = cv_validate1(h, cv, cvtype, options, cvv,
regexps, yrestype, restype, reason)) < 0)
goto done;
@ -1296,6 +1388,10 @@ yang_type_resolve(yang_stmt *yorig,
patterns, regexps,
fraction) < 0)
goto done;
if (yrestype && *yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
/* appends patterns, overwrites others if any */
if (yang_type_resolve_restrictions(ytype, options, cvv, patterns, fraction) < 0)
goto done;
@ -1303,6 +1399,10 @@ yang_type_resolve(yang_stmt *yorig,
ok:
retval = 0;
done:
#if 1
if (retval == 0 && yrestype != NULL) /* Assert that on success, yrestype is set */
assert(*yrestype);
#endif
if (prefix)
free(prefix);
if (type)
@ -1383,6 +1483,10 @@ yang_type_get(yang_stmt *ys,
if (yang_type_resolve(ys, ys, ytype, yrestype,
options, cvv, patterns, regexps, fraction) < 0)
goto done;
if (yrestype && *yrestype == NULL){
clicon_err(OE_YANG, 0, "result-type should not be NULL");
goto done;
}
retval = 0;
done:
if (type)

261
test/test_cli_leafref.sh Executable file
View file

@ -0,0 +1,261 @@
#!/usr/bin/env bash
# transitive leafref->leafref leafref->identityref completion
# 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
# include err() and new() functions and creates $dir
cfg=$dir/conf_yang.xml
fyang=$dir/example-leafref.yang
# Use yang in example
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>example-leafref</CLICON_YANG_MODULE_MAIN>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
</clixon-config>
EOF
cat <<EOF > $fyang
module example-leafref{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
identity crypto-alg {
description
"Base identity from which all crypto algorithms
are derived. (from: RFC7950 Sec 7.18 and 9.10)";
}
identity des {
base "ex:crypto-alg";
description "DES crypto algorithm.";
}
identity des2 {
base "ex:crypto-alg";
description "DES crypto algorithm.";
}
identity des3 {
base "ex:crypto-alg";
description "Triple DES crypto algorithm.";
}
/* Basic config data */
container table{
list parameter{
key name;
leaf name{
type uint32;
}
leaf value{
type string;
}
}
}
/* first level leafref */
container leafrefs {
description "Relative path";
list leafref{
key name;
leaf name {
type leafref{
path "../../../table/parameter/name";
}
}
}
}
/* first level leafref absolute */
container leafrefstop {
description "Same but absolute path";
list leafref{
key name;
leaf name {
type leafref{
path "/table/parameter/name";
}
}
}
}
/* first level leafref require-instance */
container leafrefsreqi {
description "Same but absolute path";
list leafref{
key name;
leaf name {
type leafref{
path "/table/parameter/name";
require-instance true;
}
}
}
}
/* first level identityrefs */
container identityrefs {
list identityref{
key name;
leaf name {
type identityref{
base "ex:crypto-alg";
}
}
}
}
/* second level leafref */
container leafrefs2 {
list leafref{
key name;
leaf name {
type leafref{
path "../../../leafrefs/leafref/name";
}
}
}
}
/* second level identityref */
container identityrefs2 {
list identityref{
key name;
leaf name {
type leafref{
path "../../../identityrefs/identityref/name";
}
}
}
}
}
EOF
cat <<EOF > $dir/startup_db
<${DATASTORE_TOP}>
<table xmlns="urn:example:clixon">
<parameter>
<name>91</name>
</parameter>
<parameter>
<name>92</name>
</parameter>
<parameter>
<name>93</name>
</parameter>
</table>
</${DATASTORE_TOP}>
EOF
new "test params: -f $cfg -s startup"
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 startup -f $cfg"
start_backend -s startup -f $cfg
fi
new "wait backend"
wait_backend
new "expand identityref 1st level"
expectpart "$(echo "set identityrefs identityref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "ex:des" "ex:des2" "ex:des3"
new "expand leafref 1st level"
expectpart "$(echo "set leafrefs leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93"
new "expand leafref top"
expectpart "$(echo "set leafrefstop leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93"
new "expand leafref require-instance"
expectpart "$(echo "set leafrefsreqi leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93"
# First level id/leaf refs
new "set identityref des"
expectpart "$($clixon_cli -1 -f $cfg set identityrefs identityref ex:des)" 0 "^$"
new "set identityref des3"
expectpart "$($clixon_cli -1 -f $cfg set identityrefs identityref ex:des3)" 0 "^$"
new "set leafref 91"
expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 91)" 0 "^$"
new "set leafref 93"
expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 93)" 0 "^"$
new "cli commit"
expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$"
new "set leafref str (expect failure)"
expectpart "$($clixon_cli -1 -l o -f $cfg set leafrefs leafref str)" 255 "'str' is not a number"
# Make a netconf request to set wrong type to fail in validate
new "netconf set leafref str wrong type"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><leafrefs xmlns=\"urn:example:clixon\"><leafref><name>str</name></leafref></leafrefs></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "cli validate"
expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "'str' is not a number <bad-element>name</bad-element>"
new "cli discard"
expectpart "$($clixon_cli -1 -f $cfg -l o discard)" 0 ""
new "set leafref 99 (non-existent)"
expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 99)" 0 "^"$
new "cli commit"
expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$"
# require-instance
new "set leafref require-instance 99 (non-existent)"
expectpart "$($clixon_cli -1 -f $cfg set leafrefsreqi leafref 99)" 0 "^"$
new "cli validate expect failure"
expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Leafref validation failed: No leaf 99 matching path"
new "cli discard"
expectpart "$($clixon_cli -1 -f $cfg -l o discard)" 0 ""
# Second level id/leaf refs
new "expand identityref 2nd level"
expectpart "$(echo "set identityrefs2 identityref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "ex:des" "ex:des2" "ex:des3"
new "expand leafref 2nd level"
expectpart "$(echo "set leafrefs2 leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "93" --not-- "92"
new "set identityref2 des"
expectpart "$($clixon_cli -1 -f $cfg set identityrefs2 identityref ex:des)" 0 "^$"
new "set leafref2 91"
expectpart "$($clixon_cli -1 -f $cfg set leafrefs2 leafref 91)" 0 "^$"
new "cli commit"
expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$"
new "show config"
expectpart "$($clixon_cli -1 -f $cfg -l o show config cli)" 0 "set table parameter 91" "set table parameter 92" "set table parameter 93" "set leafrefs leafref 91" "set leafrefs leafref 93" "set identityrefs identityref ex:des" "set identityrefs identityref ex:des3" "set leafrefs2 leafref 91" "set identityrefs2 identityref ex:des" --not-- "set identityrefs identityref ex:des2" "set leafrefs leafref 92"
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
rm -rf $dir
new "endtest"
endtest

View file

@ -47,11 +47,13 @@ module example{
description "Absolute references existing interfaces in if module";
type leafref {
path "/if:interfaces/if:interface/if:name";
require-instance true;
}
}
leaf relname {
type leafref {
path "../../if:interfaces/if:interface/if:name";
require-instance true;
}
}
leaf address {
@ -59,12 +61,14 @@ module example{
type leafref {
path "../../if:interfaces/if:interface[if:name = current()/../relname]"
+ "/ip:ipv4/ip:address/ip:ip";
require-instance true;
}
}
leaf wrong {
description "References leading nowhere in yang";
type leafref {
path "/ip:interfaces/ip:interface/ip:name";
require-instance true;
}
}
}
@ -76,6 +80,7 @@ module example{
leaf template{
type leafref{
path "/sender/name";
require-instance true;
}
}
}

View file

@ -59,12 +59,14 @@ module leafref{
description "For testing leafref across augment and grouping";
type leafref {
path "/ex:sender/ex:name";
require-instance true;
}
}
typedef sender-ref-local {
description "For testing leafref local";
type leafref {
path "/example:sender/example:name";
require-instance true;
}
}
list sender{
@ -83,7 +85,6 @@ module leafref{
description "top-level ref (right prefix)";
type sender-ref-local;
}
}
}
EOF
@ -113,6 +114,7 @@ module augment{
leaf name{
type leafref {
path "/ex:sender/ex:name";
require-instance true;
}
}
}

View file

@ -59,6 +59,7 @@ module leafref{
leaf ref{
type leafref {
path "/ex:sender-config/ex:name";
require-instance true;
}
}
}