* 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

@ -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\")>",
@ -254,7 +255,7 @@ yang2cli_var_identityref(yang_stmt *ys,
free(id);
return retval;
}
/*! Generate range check statements for CLI variables
* @param[in] ys Yang statement
* @param[in] options Flags field of optional values, eg YANG_OPTIONS_RANGE
@ -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 *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