diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d4ac97..0ba398cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ ## Upcoming 3.3.2 +### Known issues +* Error in CLI generated code for Yang empty types + ### Major changes: +* Added support for YANG anyxml. + * Changed top-level netconf get-config and get to return `` instead of `` to comply to the RFC. * If you use direct netconf get or get-config calls, you may need to handle the return XML differently. * RESTCONF and CLI is not affected. @@ -82,6 +87,10 @@ If you submit "nopresence" without a leaf, it will automatically be removed: * You need to define state data in a backend callback. See the example and documentation for more details. ### Minor changes: +* Corrected Yang union CLI generation and type validation. Recursive unions did not work. +* Corrected Yang pattern type escaping problem, ie '\.' did not work properly. This requires update of cligen as well. +* Compliance with RFC: Rename yang xpath to schema_nodeid and syntaxnode to datanode. +* Main yang module (CLICON_YANG_MODULE_MAIN or -y) can be an absolute file name. * Removed 'margin' parameter of yang_print(). * Extended example with ietf-routing (not only ietf-ip) for rpc operations. * Added yang dir with ietf-netconf and clixon-config yang specs for internal usage. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 7ecd2508..84389dd4 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1028,6 +1028,10 @@ from_client_msg(clicon_handle h, xml_free(xt); if (cbret) cbuf_free(cbret); + /* Sanity: log if clicon_err() is not called ! */ + if (retval < 0 && clicon_errno < 0) + clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)", + __FUNCTION__, name?name:""); return retval;// -1 here terminates backend } @@ -1060,5 +1064,5 @@ from_client(int s, done: if (msg) free(msg); - return retval; + return retval; /* -1 here terminates backend */ } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 8eb4c03b..bee83370 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -108,7 +108,7 @@ generic_validate(yang_spec *yspec, for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); - if (yang_mandatory(ys)){ + if (ys && yang_mandatory(ys)){ clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", xml_name(x1)); goto done; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ec560db4..ccfae5cc 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -431,13 +431,8 @@ main(int argc, char **argv) case 'p' : /* Print spec */ printspec++; break; - case 'y' :{ /* yang module */ - /* Set revision to NULL, extract dir and module */ - char *str = strdup(optarg); - char *dir = dirname(str); - hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION"); - clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg)); - clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir)); + case 'y' :{ /* Override yang module or absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } case 'x' :{ /* xmldb plugin */ diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 5fb4cfa6..3cf8d04e 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -189,44 +189,15 @@ yang2cli_var_sub(clicon_handle h, yang_stmt *yi = NULL; int i = 0; char *cvtypestr; - int completion; - /* enumeration already gives completion */ if (cvtype == CGV_VOID){ retval = 0; goto done; } type = ytype?ytype->ys_argument:NULL; - if (type) - completion = clicon_cli_genmodel_completion(h) && - strcmp(type, "enumeration") != 0 && - strcmp(type, "bits") != 0; - else - completion = clicon_cli_genmodel_completion(h); - - if (completion) - cprintf(cb0, "("); cvtypestr = cv_type2str(cvtype); cprintf(cb0, "<%s:%s", ys->ys_argument, cvtypestr); -#if 0 - if (type && (strcmp(type, "identityref") == 0)){ - yang_stmt *ybase; - if ((ybase = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL){ - cprintf(cb0, " choice:"); - i = 0; - /* for every found identity derived from base-type , do: */ - { - if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT) - continue; - if (i) - cprintf(cb0, "|"); - cprintf(cb0, "%s", yi->ys_argument); - i++; - } - } - - } -#endif + /* enumeration special case completion */ if (type && (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0)){ cprintf(cb0, " choice:"); i = 0; @@ -242,6 +213,7 @@ yang2cli_var_sub(clicon_handle h, if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb0, " fraction-digits:%u", fraction_digits); if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){ + assert(mincv || maxcv); cprintf(cb0, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); if (mincv){ if ((r = cv2str_dup(mincv)) == NULL){ @@ -250,13 +222,13 @@ yang2cli_var_sub(clicon_handle h, } cprintf(cb0, "%s:", r); free(r); + r = NULL; } if (maxcv != NULL){ if ((r = cv2str_dup(maxcv)) == NULL){ clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } - } else{ /* Cligen does not have 'max' keyword in range so need to find actual max value of type if yang range expression is 0..max */ @@ -267,6 +239,7 @@ yang2cli_var_sub(clicon_handle h, } cprintf(cb0, "%s]", r); free(r); + r = NULL; } if (options & YANG_OPTIONS_PATTERN) cprintf(cb0, " regexp:\"%s\"", pattern); @@ -274,30 +247,74 @@ yang2cli_var_sub(clicon_handle h, cprintf(cb0, ">"); if (helptext) cprintf(cb0, "(\"%s\")", helptext); - if (completion){ -#if 0 - if (type && (strcmp(type, "leafref") == 0)){ - yang_stmt *ypath; - /* XXX only for absolute xpath */ - if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ - clicon_err(OE_XML, 0, "leafref should have path sub"); - goto done; - } - clicon_debug(1, "%s ypath:%s\n", __FUNCTION__, ypath->ys_argument); - cprintf(cb0, "|<%s:%s", ys->ys_argument, - cv_type2str(cvtype)); - cprintf(cb0, " %s(\"candidate\",\"%s\")>", - GENERATE_EXPAND_XMLDB, - ypath->ys_argument); - } - else -#endif - if (cli_expand_var_generate(h, ys, cvtype, cb0, - options, fraction_digits) < 0) - goto done; - if (helptext) - cprintf(cb0, "(\"%s\")", helptext); - cprintf(cb0, ")"); + retval = 0; + done: + return retval; +} + +/* forward */ +static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, cbuf *cb0, + char *helptext, yang_stmt *yrestype, char *type); + +static int +yang2cli_var_union_one(clicon_handle h, + yang_stmt *ys, + cbuf *cb0, + char *helptext, + char *type, + yang_stmt *yt) +{ + int retval = -1; + int options = 0; + cg_var *mincv = NULL; + cg_var *maxcv = NULL; + char *pattern = NULL; + uint8_t fraction_digits = 0; + enum cv_type cvtype; + yang_stmt *yrestype; + char *restype; + + if (yang_type_resolve(ys, yt, &yrestype, + &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) + goto done; + restype = yrestype?yrestype->ys_argument:NULL; + + if (restype && strcmp(restype, "union") == 0){ /* recursive union */ + if (yang2cli_var_union(h, ys, cb0, helptext, yrestype, type) < 0) + goto done; + } + else { + if (clicon_type2cv(type, restype, &cvtype) < 0) + goto done; + if ((retval = yang2cli_var_sub(h, ys, cb0, helptext, cvtype, yrestype, + options, mincv, maxcv, pattern, fraction_digits)) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +static int +yang2cli_var_union(clicon_handle h, + yang_stmt *ys, + cbuf *cb0, + char *helptext, + yang_stmt *yrestype, + char *type) +{ + int retval = -1; + yang_stmt *yt = NULL; + int i; + + i = 0; + while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){ + if (yt->ys_keyword != Y_TYPE) + continue; + if (i++) + cprintf(cb0, "|"); + if (yang2cli_var_union_one(h, ys, cb0, helptext, type, yt) < 0) + goto done; } retval = 0; done: @@ -322,12 +339,10 @@ yang2cli_var(clicon_handle h, cg_var *mincv = NULL; cg_var *maxcv = NULL; char *pattern = NULL; - yang_stmt *yt = NULL; - yang_stmt *yrt; uint8_t fraction_digits = 0; enum cv_type cvtype; int options = 0; - int i; + int completionp; if (yang_type_get(ys, &type, &yrestype, &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) @@ -335,35 +350,45 @@ yang2cli_var(clicon_handle h, restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) goto done; + /* Note restype can be NULL here for example with unresolved hardcoded uuid */ if (restype && strcmp(restype, "union") == 0){ - /* Union: loop over resolved type's sub-types */ + /* Union: loop over resolved type's sub-types (can also be recursive unions) */ cprintf(cb0, "("); - yt = NULL; - i = 0; - while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){ - if (yt->ys_keyword != Y_TYPE) - continue; - if (i++) - cprintf(cb0, "|"); - if (yang_type_resolve(ys, yt, &yrt, - &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) + if (yang2cli_var_union(h, ys, cb0, helptext, yrestype, type) < 0) + goto done; + if (clicon_cli_genmodel_completion(h)){ + if (cli_expand_var_generate(h, ys, cvtype, cb0, + options, fraction_digits) < 0) goto done; - restype = yrt?yrt->ys_argument:NULL; - if (clicon_type2cv(type, restype, &cvtype) < 0) - goto done; - if ((retval = yang2cli_var_sub(h, ys, cb0, helptext, cvtype, yrt, - options, mincv, maxcv, pattern, fraction_digits)) < 0) - - goto done; - + if (helptext) + cprintf(cb0, "(\"%s\")", helptext); } cprintf(cb0, ")"); } - else + else{ + char *type; + type = yrestype?yrestype->ys_argument:NULL; + if (type) + completionp = clicon_cli_genmodel_completion(h) && + strcmp(type, "enumeration") != 0 && + strcmp(type, "bits") != 0; + else + completionp = clicon_cli_genmodel_completion(h); + if (completionp) + cprintf(cb0, "("); if ((retval = yang2cli_var_sub(h, ys, cb0, helptext, cvtype, yrestype, options, mincv, maxcv, pattern, fraction_digits)) < 0) goto done; + if (completionp){ + if (cli_expand_var_generate(h, ys, cvtype, cb0, + options, fraction_digits) < 0) + goto done; + if (helptext) + cprintf(cb0, "(\"%s\")", helptext); + cprintf(cb0, ")"); + } + } retval = 0; done: diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 79b0a5ad..a9a17073 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -291,14 +291,8 @@ main(int argc, char **argv) case 'L' : /* Debug print dynamic CLI syntax */ logclisyntax++; break; - case 'y' :{ /* yang module */ - /* Set revision to NULL, extract dir and module */ - char *str = strdup(optarg); - char *dir = dirname(str); - hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION"); - clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg)); - clicon_option_str_set(h, "CLICON_YANG_DIR", dir); - free(str); + case 'y' :{ /* Overwrite yang module or absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } default: diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 394c5d03..e8c87d3b 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -628,7 +628,12 @@ clicon_parse(clicon_handle h, parse_tree *pt; /* Orig */ cg_obj *match_obj; cvec *cvv = NULL; + FILE *f; + if (clicon_get_logflags()&CLICON_LOG_STDOUT) + f = stdout; + else + f = stderr; stx = cli_syntax(h); m = *mode; if (m == NULL) { @@ -637,7 +642,7 @@ clicon_parse(clicon_handle h, } else { if ((smode = syntax_mode_find(stx, m, 0)) == NULL) { - cli_output(stderr, "Can't find syntax mode '%s'\n", m); + cli_output(f, "Can't find syntax mode '%s'\n", m); return -1; } } @@ -663,10 +668,11 @@ clicon_parse(clicon_handle h, free(msav); } msav = NULL; + switch (res) { case CG_EOF: /* eof */ case CG_ERROR: - cli_output(stderr, "CLI parse error: %s\n", cmd); + cli_output(f, "CLI parse error: %s\n", cmd); goto done; case CG_NOMATCH: /* no match */ smode = NULL; @@ -676,10 +682,12 @@ clicon_parse(clicon_handle h, if ((smode = syntax_mode_find(stx, m, 0)) != NULL) continue; else - cli_output(stderr, "Can't find syntax mode '%s'\n", m); + cli_output(f, "Can't find syntax mode '%s'\n", m); } } - cli_output(stderr, "CLI syntax error: \"%s\": %s\n", + /* clicon_err(OE_CFG, 0, "CLI syntax error: \"%s\": %s", + cmd, cli_nomatch(h));*/ + cli_output(f, "CLI syntax error: \"%s\": %s\n", cmd, cli_nomatch(h)); break; case CG_MATCH: @@ -695,7 +703,7 @@ clicon_parse(clicon_handle h, goto done; break; default: - cli_output(stderr, "CLI syntax error: \"%s\" is ambiguous\n", cmd); + cli_output(f, "CLI syntax error: \"%s\" is ambiguous\n", cmd); goto done; break; } diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 5826393a..52e8be2c 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -359,13 +359,8 @@ main(int argc, char **argv) usage(h, argv[0]); clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg); break; - case 'y' :{ /* yang module */ - /* Set revision to NULL, extract dir and module */ - char *str = strdup(optarg); - char *dir = dirname(str); - hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION"); - clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg)); - clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir)); + case 'y' :{ /* Overwrite yang module or absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } default: diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index cd778f15..0c8d519c 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -320,14 +320,9 @@ main(int argc, goto done; /* Overwrite yang module with -y option */ - if (yangspec){ - /* Set revision to NULL, extract dir and module */ - char *str = strdup(yangspec); - char *dir = dirname(str); - hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION"); - clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(yangspec)); - clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir)); - } + if (yangspec) + clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", yangspec); + /* Initialize plugins group */ if (restconf_plugin_load(h) < 0) return -1; diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp index f0a5afb0..11102feb 100644 --- a/clixon.conf.cpp.cpp +++ b/clixon.conf.cpp.cpp @@ -45,17 +45,15 @@ # Location of configuration-file for default values (this file) CLICON_CONFIGFILE sysconfdir/APPNAME.conf -# Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG +# Location of YANG module and submodule files. CLICON_YANG_DIR prefix/share/APPNAME/yang -# Option used to construct initial yang file: -# [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG +# Main yang module or absolute filename. If module then search as follows: +# /[@] # CLICON_YANG_MODULE_MAIN clicon # Option used to construct initial yang file: # [@] -# This option is only relevant if CLICON_DBSPEC_TYPE is YANG CLICON_YANG_MODULE_REVISION # Location of backend .so plugins diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index b15fe7ba..3fb090c0 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -327,7 +327,7 @@ get(char *dbname, } } else - if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ + if ((y = yang_find_datanode((yang_node*)y, name)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -760,7 +760,7 @@ put(char *dbfile, } /* For every node, create a key with values */ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((y = yang_find_syntax((yang_node*)ys, xml_name(x))) == NULL){ + if ((y = yang_find_datanode((yang_node*)ys, xml_name(x))) == NULL){ clicon_err(OE_UNIX, 0, "No yang node found: %s", xml_name(x)); goto done; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 787a32fb..386be2c9 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -543,7 +543,7 @@ text_modify(cxobj *x0, break; } /* switch op */ } /* if LEAF|LEAF_LIST */ - else { /* eg Y_CONTAINER, Y_LIST */ + else { /* eg Y_CONTAINER, Y_LIST, Y_ANYXML */ switch(op){ case OP_CREATE: if (x0){ @@ -555,8 +555,21 @@ text_modify(cxobj *x0, xml_purge(x0); x0 = NULL; } - case OP_NONE: /* fall thru */ - case OP_MERGE: + case OP_MERGE: /* fall thru */ + case OP_NONE: + /* Special case: anyxml, just replace tree, + See 7.10.3 of RFC6020bis */ + if (y0->yn_keyword == Y_ANYXML){ + if (op == OP_NONE) + break; + if (x0) + xml_purge(x0); + if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) + goto done; + if (xml_copy(x1, x0) < 0) + goto done; + break; + } if (x0==NULL){ if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) goto done; @@ -568,7 +581,7 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_syntax(y0, x1cname)) == NULL){ + if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); goto done; } @@ -690,12 +703,9 @@ xml_container_presence(cxobj *x, void *arg) { int retval = -1; - char *name; yang_stmt *y; /* yang node */ - name = xml_name(x); if ((y = (yang_stmt*)xml_spec(x)) == NULL){ - clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, name); retval = 0; goto done; } diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index 9b225ebf..4e45e351 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -60,6 +60,7 @@ extern int debug; * Prototypes */ int clicon_log_init(char *ident, int upto, int flags); +int clicon_get_logflags(void); int clicon_log_str(int level, char *msg); int clicon_log(int level, char *format, ...); clicon_log_notify_t *clicon_log_register_callback(clicon_log_notify_t *cb, void *arg); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index f4ed4c07..207b8654 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -90,10 +90,11 @@ char *xml_value_append(cxobj *xn, char *val); enum cxobj_type xml_type(cxobj *xn); int xml_type_set(cxobj *xn, enum cxobj_type type); -cg_var *xml_cv_get(cxobj *xn); -int xml_cv_set(cxobj *xn, cg_var *cv); +cg_var *xml_cv_get(cxobj *xn); +int xml_cv_set(cxobj *xn, cg_var *cv); int xml_child_nr(cxobj *xn); +int xml_child_nr_type(cxobj *xn, enum cxobj_type type); cxobj *xml_child_i(cxobj *xn, int i); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index fc22bb00..fb8d49a8 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -124,8 +124,11 @@ enum rfc_6020{ #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ -/* Yang syntz nodes */ -#define yang_is_syntax(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST) +/* Yang data node */ +#define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML) + +/* Yang schema node */ +#define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION) typedef struct yang_stmt yang_stmt; /* forward */ @@ -201,7 +204,7 @@ yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); -yang_stmt *yang_find_syntax(yang_node *yn, char *argument); +yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name); int yang_print(FILE *f, yang_node *yn); @@ -210,8 +213,9 @@ int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); -yang_node *yang_xpath_abs(yang_node *yn, char *xpath); -yang_node *yang_xpath(yang_node *yn, char *xpath); +yang_node *yang_abs_schema_nodeid(yang_node *yn, char *schema_nodeid); +yang_node *yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid); +yang_node *yang_schema_nodeid(yang_node *yn, char *xpath); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys); int yang_mandatory(yang_stmt *ys); diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 3b840a41..bca91b97 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -97,6 +97,12 @@ clicon_log_init(char *ident, return 0; } +int +clicon_get_logflags(void) +{ + return _logflags; +} + /*! Register log callback, return old setting */ clicon_log_notify_t * @@ -153,7 +159,7 @@ slogtime(void) /*! Make a logging call to syslog (or stderr). * * This is the _only_ place the actual syslog (or stderr) logging is made in clicon,.. - * @note yslog makes itw own filtering, but if log to stderr we do it here + * @note syslog makes itw own filtering, but if log to stderr we do it here * @see clicon_debug() * * @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index ecd6863a..7a648eda 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -392,7 +392,6 @@ clicon_configfile(clicon_handle h) return clicon_option_str(h, "CLICON_CONFIGFILE"); } - /*! YANG database specification directory */ char * clicon_yang_dir(clicon_handle h) @@ -400,7 +399,7 @@ clicon_yang_dir(clicon_handle h) return clicon_option_str(h, "CLICON_YANG_DIR"); } -/*! YANG main module */ +/*! YANG main module or absolute file name */ char * clicon_yang_module_main(clicon_handle h) { diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index c7b95e16..ba1a4e34 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -492,7 +492,7 @@ clicon_rpc(int s, char **ret) { int retval = -1; - struct clicon_msg *reply; + struct clicon_msg *reply = NULL; int eof; char *data = NULL; cxobj *cx = NULL; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 44a9dcf4..355e2834 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -351,6 +351,23 @@ xml_child_nr(cxobj *xn) return xn->x_childvec_len; } +/*! Get number of children of specific type + * @param[in] xn xml node + * @param[in] type XML type or -1 for all + * @retval number of typed children in XML tree + */ +int +xml_child_nr_type(cxobj *xn, + enum cxobj_type type) +{ + cxobj *x = NULL; + int len = 0; + + while ((x = xml_child_each(xn, x, type)) != NULL) + len++; + return len; +} + /*! Get a specific child * @param[in] xn xml node * @param[in] i the number of the child, eg order in children vector @@ -516,6 +533,9 @@ xml_new_spec(char *name, return x; } +/*! Return yang spec of node. + * Not necessarily set. Either has not been set yet (by xml_spec_set( or anyxml. + */ void * xml_spec(cxobj *x) { @@ -939,7 +959,7 @@ clicon_xml2cbuf(cbuf *cb, while ((xc = xml_child_each(x, xc, CX_ATTR)) != NULL) clicon_xml2cbuf(cb, xc, level+1, prettyprint); /* Check for special case instead of */ - if (xml_body(x)==NULL && xml_child_nr(x)==0) + if (xml_body(x)==NULL && xml_child_nr_type(x, CX_ELMNT)==0) cprintf(cb, "/>"); else{ cprintf(cb, ">"); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2639f6df..01fa5dcc 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -340,50 +340,51 @@ xml_yang_validate_add(cxobj *xt, char *body; /* if not given by argument (overide) use default link */ - ys = xml_spec(xt); - switch (ys->ys_keyword){ - case Y_LIST: - /* fall thru */ - case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = ys->ys_stmt[i]; - if (yc->ys_keyword != Y_LEAF) - continue; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", - yc->ys_argument); + if ((ys = xml_spec(xt)) != NULL){ + switch (ys->ys_keyword){ + case Y_LIST: + /* fall thru */ + case Y_CONTAINER: + for (i=0; iys_len; i++){ + yc = ys->ys_stmt[i]; + if (yc->ys_keyword != Y_LEAF) + continue; + if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ + clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", + yc->ys_argument); + goto done; + } + } + break; + case Y_LEAF: + /* fall thru */ + case Y_LEAF_LIST: + /* validate value against ranges, etc */ + if ((cv = cv_dup(ys->ys_cv)) == NULL){ + clicon_err(OE_UNIX, errno, "cv_dup"); goto done; } - } - break; - case Y_LEAF: - /* fall thru */ - case Y_LEAF_LIST: - /* validate value against ranges, etc */ - if ((cv = cv_dup(ys->ys_cv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; - } - /* In the union case, value is parsed as generic REST type, - * needs to be reparsed when concrete type is selected - */ - if ((body = xml_body(xt)) != NULL){ - if (cv_parse(body, cv) <0){ - clicon_err(OE_UNIX, errno, "cv_parse"); - goto done; - } - if ((ys_cv_validate(cv, ys, &reason)) != 1){ - clicon_err(OE_DB, 0, - "validation of %s failed %s", - ys->ys_argument, reason?reason:""); - if (reason) - free(reason); - goto done; + /* In the union case, value is parsed as generic REST type, + * needs to be reparsed when concrete type is selected + */ + if ((body = xml_body(xt)) != NULL){ + if (cv_parse(body, cv) <0){ + clicon_err(OE_UNIX, errno, "cv_parse"); + goto done; + } + if ((ys_cv_validate(cv, ys, &reason)) != 1){ + clicon_err(OE_DB, 0, + "validation of %s failed %s", + ys->ys_argument, reason?reason:""); + if (reason) + free(reason); + goto done; + } } + break; + default: + break; } - break; - default: - break; } retval = 0; done: @@ -408,21 +409,22 @@ xml_yang_validate_all(cxobj *xt, yang_stmt *ytype; /* if not given by argument (overide) use default link */ - ys = xml_spec(xt); - switch (ys->ys_keyword){ - case Y_LEAF: - /* fall thru */ - case Y_LEAF_LIST: - /* Special case if leaf is leafref, then first check against - current xml tree - */ - if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL && - strcmp(ytype->ys_argument, "leafref") == 0) - if (validate_leafref(xt, ytype) < 0) - goto done; - break; - default: - break; + if ((ys = xml_spec(xt)) != NULL){ + switch (ys->ys_keyword){ + case Y_LEAF: + /* fall thru */ + case Y_LEAF_LIST: + /* Special case if leaf is leafref, then first check against + current xml tree + */ + if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL && + strcmp(ytype->ys_argument, "leafref") == 0) + if (validate_leafref(xt, ytype) < 0) + goto done; + break; + default: + break; + } } retval = 0; done: @@ -477,7 +479,7 @@ xml2cvec(cxobj *xt, /* Go through all children of the xml tree */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){ name = xml_name(xc); - if ((ys = yang_find_syntax((yang_node*)yt, name)) == NULL){ + if ((ys = yang_find_datanode((yang_node*)yt, name)) == NULL){ clicon_debug(0, "%s: yang sanity problem: %s in xml but not present in yang under %s", __FUNCTION__, name, yt->ys_argument); if ((body = xml_body(xc)) != NULL){ @@ -592,11 +594,14 @@ xml_is_body(cxobj *xt, char *val) { cxobj *x; + char *bx; x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if (strcmp(name, xml_name(x))) continue; + if ((bx = xml_body(x)) == NULL) + continue; if (strcmp(xml_body(x), val) == 0) return 1; } @@ -641,7 +646,7 @@ xml_diff1(yang_stmt *ys, if (ys->ys_keyword == Y_SPEC) y = yang_find_topnode((yang_spec*)ys, name); else - y = yang_find_syntax((yang_node*)ys, name); + y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; @@ -719,6 +724,10 @@ xml_diff1(yang_stmt *ys, goto done; break; } + body1 = xml_body(x1); + body2 = xml_body(x2); + if (body1 == NULL || body2 == NULL) /* empty type */ + break; if (strcmp(xml_body(x1), xml_body(x2))){ if (cxvec_append(x1, changed1, changedlen) < 0) goto done; @@ -728,7 +737,8 @@ xml_diff1(yang_stmt *ys, } break; case Y_LEAF_LIST: - body1 = xml_body(x1); + if ((body1 = xml_body(x1)) == NULL) + continue; if (!xml_is_body(xt2, name, body1)) /* where body is */ if (cxvec_append(x1, first, firstlen) < 0) goto done; @@ -746,7 +756,7 @@ xml_diff1(yang_stmt *ys, if (ys->ys_keyword == Y_SPEC) y = yang_find_topnode((yang_spec*)ys, name); else - y = yang_find_syntax((yang_node*)ys, name); + y = yang_find_datanode((yang_node*)ys, name); if (y == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; @@ -1197,7 +1207,7 @@ xml_tree_prune_flagged_sub(cxobj *xt, yang_node *yt; mark = 0; - yt = xml_spec(xt); + yt = xml_spec(xt); /* xan be null */ x = NULL; xprev = x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { @@ -1306,7 +1316,6 @@ xml_default(cxobj *xt, char *str; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ - clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); retval = 0; goto done; } @@ -1359,7 +1368,6 @@ xml_order(cxobj *xt, char *xname; /* xml child name */ if ((y = (yang_stmt*)xml_spec(xt)) == NULL){ - clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); retval = 0; goto done; } @@ -1367,7 +1375,7 @@ xml_order(cxobj *xt, /* Go through xml children and ensure they are same order as yspec children */ for (i=0; iys_len; i++){ yc = y->ys_stmt[i]; - if (!yang_is_syntax(yc)) + if (!yang_datanode(yc)) continue; yname = yc->ys_argument; /* First go thru xml children with same name */ @@ -1411,7 +1419,6 @@ xml_sanity(cxobj *xt, char *name; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ - clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); retval = 0; goto done; } @@ -1438,7 +1445,6 @@ xml_non_config_data(cxobj *xt, yang_stmt *ys; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ - clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); retval = 0; goto done; } @@ -1455,6 +1461,7 @@ xml_non_config_data(cxobj *xt, /*! Add yang specification backpoint to XML node * @param[in] xt XML tree node * @note This should really be unnecessary since yspec should be set on creation + * @note For subs to anyxml nodes will not have spec set * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode @@ -1466,25 +1473,28 @@ xml_spec_populate(cxobj *x, int retval = -1; yang_spec *yspec = (yang_spec*)arg; char *name; - yang_stmt *y; /* yang node */ - cxobj *xp; /* xml parent */ - yang_stmt *yp; /* parent yang */ + yang_stmt *y=NULL; /* yang node */ + cxobj *xp; /* xml parent */ + yang_stmt *yp; /* parent yang */ name = xml_name(x); if ((xp = xml_parent(x)) != NULL && (yp = xml_spec(xp)) != NULL) - y = yang_find_syntax((yang_node*)yp, xml_name(x)); + y = yang_find_datanode((yang_node*)yp, xml_name(x)); else y = yang_find_topnode(yspec, name); /* still NULL for config */ +#ifdef XXX_OBSOLETE /* Add validate elsewhere */ if (y==NULL){ clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", name, xp?xml_name(xp):"", yp?yp->ys_argument:""); goto done; } - xml_spec_set(x, y); +#endif + if (y) + xml_spec_set(x, y); retval = 0; - done: + // done: return retval; } @@ -1541,7 +1551,7 @@ api_path2xpath_cvv(yang_spec *yspec, } else{ assert(y!=NULL); - if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){ + if ((y = yang_find_datanode((yang_node*)y, name)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1671,7 +1681,7 @@ api_path2xml_vec(char **vec, if (y0->yn_keyword == Y_SPEC) /* top-node */ y = yang_find_topnode((yang_spec*)y0, name); else - y = yang_find_syntax((yang_node*)y0, name); + y = yang_find_datanode((yang_node*)y0, name); if (y == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", name); goto done; @@ -1936,7 +1946,7 @@ xml_merge1(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_syntax(y0, x1cname)) == NULL){ + if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); goto done; } diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index d8a0e940..a3b73de0 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -376,7 +376,7 @@ yn_each(yang_node *yn, * @param[in] argument String compare w wrgument. if NULL, match any. * This however means that if you actually want to match only a yang-stmt with * argument==NULL you cannot, but I have not seen any such examples. - * @see yang_find_syntax + * @see yang_find_datanode */ yang_stmt * yang_find(yang_node *yn, @@ -402,20 +402,17 @@ yang_find(yang_node *yn, return match ? ys : NULL; } -/*! Find a child syntax-node yang_stmt with matching argument (container, leaf, etc) +/*! Find child data node with matching argument (container, leaf, etc) * * @param[in] yn Yang node, current context node. - * @param[in] argument if NULL, match any(first) argument. + * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? * - * @see yang_find But this looks only for the yang specification nodes with - * the following keyword: container, leaf, list, leaf-list - * That is, basic syntax nodes. - * @note check if argument==NULL really required? - * Is this yang-stmt a container, list, leaf or leaf-list? + * @see yang_find Looks for any node + * @note May deviate from RFC since it explores choice/case not just return it. */ yang_stmt * -yang_find_syntax(yang_node *yn, - char *argument) +yang_find_datanode(yang_node *yn, + char *argument) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -428,9 +425,9 @@ yang_find_syntax(yang_node *yn, for (j=0; jys_len; j++){ yc = ys->ys_stmt[j]; if (yc->ys_keyword == Y_CASE) /* Look for its children */ - ysmatch = yang_find_syntax((yang_node*)yc, argument); + ysmatch = yang_find_datanode((yang_node*)yc, argument); else - if (yang_is_syntax(yc)){ + if (yang_datanode(yc)){ if (argument == NULL) ysmatch = yc; else @@ -440,9 +437,9 @@ yang_find_syntax(yang_node *yn, if (ysmatch) goto match; } - } + } /* Y_CHOICE */ else - if (yang_is_syntax(ys)){ + if (yang_datanode(ys)){ if (argument == NULL) ysmatch = ys; else @@ -456,7 +453,10 @@ yang_find_syntax(yang_node *yn, return ysmatch; } -/*! Help function to check find 'top-node', eg first 'syntax' node in a spec +/*! Find 'top-node', eg first data node in all (sub)modules in a yang spec + * + * @param[in] ysp Yang specification + * @param[in] name if NULL, match any(first) argument. XXX is that really a case? * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for syntax-nodes. Note that if a child to a module is a choice, @@ -472,24 +472,23 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if ((yc = yang_find_syntax((yang_node*)ys, name)) != NULL) + if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) return yc; } return NULL; } -/*! Find a child spec-node yang_stmt with matching argument for xpath +/*! Find a child spec-node yang_stmt with matching argument for schema-nodid * * See also yang_find() but this looks only for the yang specification nodes with * the following keyword: container, leaf, list, leaf-list * That is, basic syntax nodes. - * @see yang_find_syntax # Maybe this is the same as specnode? + * @see yang_find_datanode * @see clicon_dbget_xpath - * @see xpath_vec */ static yang_stmt * -yang_find_xpath_stmt(yang_node *yn, - char *argument) +schema_nodeid_stmt(yang_node *yn, + char *argument) { yang_stmt *ys = NULL; int i; @@ -497,23 +496,19 @@ yang_find_xpath_stmt(yang_node *yn, for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; + if (!yang_schemanode(ys)) + continue; /* some keys dont have arguments, match on key */ if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ - if (strcmp(argument, yang_key2str(ys->ys_keyword)) == 0) + if (strcmp(argument, yang_key2str(ys->ys_keyword)) == 0){ match++; - } - else - if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LEAF || - ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST || - ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE || - ys->ys_keyword == Y_RPC || ys->ys_keyword == Y_CHOICE || - ys->ys_keyword == Y_CASE - ){ - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + break; + } + } else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0){ match++; - } - if (match) - break; + break; + } } return match ? ys : NULL; } @@ -966,21 +961,6 @@ ys_populate_type(yang_stmt *ys, goto done; } } - else - if (strcmp(ys->ys_argument, "leafref") == 0){ -#ifdef notyet /* XXX: Do augment first */ - yang_stmt *ypath; - if ((ypath = yang_find((yang_node*)ys, Y_PATH, NULL)) == NULL){ - clicon_err(OE_YANG, 0, "leafref type requires path sub-statement"); - goto done; - } - if (yang_xpath((yang_node*)ys, ypath->ys_argument) == NULL){ - clicon_err(OE_YANG, 0, "Leafref path %s not found", - ypath->ys_argument); - goto done; - } -#endif - } retval = 0; done: return retval; @@ -1101,17 +1081,17 @@ yang_augment_node(yang_stmt *ys, yang_spec *ysp) { int retval = -1; - char *path; + char *schema_nodeid; yang_node *yn; yang_stmt *yc; int i; - path = ys->ys_argument; - clicon_debug(1, "%s %s", __FUNCTION__, path); + schema_nodeid = ys->ys_argument; + clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); /* Find the target */ - if ((yn = yang_xpath_abs((yang_node*)ys, path)) == NULL){ - clicon_err(OE_YANG, 0, "Augment path %s not found", path); + if ((yn = yang_abs_schema_nodeid((yang_node*)ys, schema_nodeid)) == NULL){ + clicon_err(OE_YANG, 0, "Augment schema_nodeid %s not found", schema_nodeid); // retval = 0; /* Ignore, continue */ goto done; } @@ -1269,8 +1249,7 @@ yang_expand(yang_node *yn) * @retval NULL Error encountered * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them - * yang_parse2 # Find file from yang (sub)module + * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc @@ -1322,8 +1301,7 @@ yang_parse_str(clicon_handle h, * Similar to clicon_yang_str(), just read a file first * (cloned from cligen) * @param h CLICON handle - * @param f Open file handle - * @param name Log string, typically filename + * @param filename Name of file * @param ysp Yang specification. Should ave been created by caller using yspec_new * @retval ymod Top-level yang (sub)module * @retval NULL Error encountered @@ -1331,26 +1309,35 @@ yang_parse_str(clicon_handle h, * The database symbols are inserted in alphabetical order. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them - * yang_parse2 # Find file from yang (sub)module + * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * yang_parse_file(clicon_handle h, - FILE *f, - const char *name, /* just for errs */ + const char *filename, yang_spec *ysp ) { - char *buf; + char *buf = NULL; int i; int c; int len; yang_stmt *ymod = NULL; + FILE *f = NULL; + struct stat st; + + clicon_debug(1, "Yang parse file: %s", filename); + if (stat(filename, &st) < 0){ + clicon_err(OE_YANG, errno, "%s not found", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL){ + clicon_err(OE_UNIX, errno, "fopen(%s)", filename); + goto done; + } - clicon_debug(1, "Yang parse file: %s", name); len = 1024; /* any number is fine */ if ((buf = malloc(len)) == NULL){ perror("pt_file malloc"); @@ -1372,9 +1359,11 @@ yang_parse_file(clicon_handle h, } buf[i++] = (char)(c&0xff); } /* read a line */ - if ((ymod = yang_parse_str(h, buf, name, ysp)) < 0) + if ((ymod = yang_parse_str(h, buf, filename, ysp)) < 0) goto done; done: + if (f) + fclose(f); if (buf) free(buf); return ymod; /* top-level (sub)module */ @@ -1431,105 +1420,62 @@ yang_parse_find_match(clicon_handle h, return retval; } -/*! Find and open yang file and then parse it - * - * @param h CLICON handle - * @param yang_dir Directory where all YANG module files reside - * @param module Name of main YANG module. More modules may be parsed if imported - * @param revision Optional module revision date - * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval ymod Top-level yang (sub)module - * @retval NULL Error encountered - * module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) - * Calling order: - * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them - * yang_parse2 # Find file from yang (sub)module - * yang_parse_file # Read yang file into a string - * yang_parse_str # Set up yacc parser and call it given a string - * clixon_yang_parseparse # Actual yang parsing using yacc - */ -static yang_stmt * -yang_parse2(clicon_handle h, - const char *yang_dir, - const char *module, - const char *revision, - yang_spec *ysp) -{ - FILE *f = NULL; - cbuf *fbuf = NULL; - char *filename; - yang_stmt *ymod = NULL; - struct stat st; - int nr; - - if ((fbuf = cbuf_new()) == NULL){ - clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); - goto done; - } - if (revision) - cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision); - else{ - /* No specific revision, Match a yang file */ - if ((nr = yang_parse_find_match(h, yang_dir, module, fbuf)) < 0) - goto done; - if (nr == 0){ - clicon_err(OE_YANG, errno, "No matching %s yang files found", module); - goto done; - } - } - filename = cbuf_get(fbuf); - if (stat(filename, &st) < 0){ - clicon_err(OE_YANG, errno, "%s not found", filename); - goto done; - } - if ((f = fopen(filename, "r")) == NULL){ - clicon_err(OE_UNIX, errno, "fopen(%s)", filename); - goto done; - } - if ((ymod = yang_parse_file(h, f, filename, ysp)) == NULL) - goto done; - done: - if (fbuf) - cbuf_free(fbuf); - if (f) - fclose(f); - return ymod; /* top-level (sub)module */ -} - /*! Parse one yang module then go through (sub)modules and parse them recursively * - * @param h CLICON handle - * @param yang_dir Directory where all YANG module files reside - * @param module Name of main YANG module. More modules may be parsed if imported - * @param revision Optional module revision date - * @param ysp Yang specification. Should have been created by caller using yspec_new - * @retval ymod Top-level yang (sub)module - * @retval NULL Error encountered + * @param[in] h CLICON handle + * @param[in] yang_dir Directory where all YANG module files reside + * @param[in] module Name of main YANG module. Or absolute file name. + * @param[in] revision Optional module revision date + * @param[in] ysp Yang specification. Should have been created by caller using yspec_new + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * Find a yang module file, and then recursively parse all its imported modules. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them - * yang_parse2 # Find file from yang (sub)module + * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc */ static yang_stmt * -yang_parse1(clicon_handle h, - const char *yang_dir, - const char *module, - const char *revision, - yang_spec *ysp) +yang_parse_recurse(clicon_handle h, + const char *yang_dir, + const char *module, + const char *revision, + yang_spec *ysp) { yang_stmt *yi = NULL; /* import */ yang_stmt *ymod; yang_stmt *yrev; char *modname; char *subrevision; + cbuf *fbuf = NULL; + int nr; + + if (module[0] == '/'){ + if ((ymod = yang_parse_file(h, module, ysp)) == NULL) + goto done; + } + else { + if ((fbuf = cbuf_new()) == NULL){ + clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__); + goto done; + } + if (revision) + cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision); + else{ + /* No specific revision, Match a yang file */ + if ((nr = yang_parse_find_match(h, yang_dir, module, fbuf)) < 0) + goto done; + if (nr == 0){ + clicon_err(OE_YANG, errno, "No matching %s yang files found", module); + goto done; + } + } + if ((ymod = yang_parse_file(h, cbuf_get(fbuf), ysp)) == NULL) + goto done; + } - if ((ymod = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL) - goto done; /* go through all import statements of ysp (or its module) */ while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){ if (yi->ys_keyword != Y_IMPORT) @@ -1540,30 +1486,33 @@ yang_parse1(clicon_handle h, else subrevision = NULL; if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL) - if (yang_parse1(h, yang_dir, modname, subrevision, ysp) == NULL){ + /* recursive call */ + if (yang_parse_recurse(h, yang_dir, modname, subrevision, ysp) == NULL){ ymod = NULL; goto done; } } done: + if (fbuf) + cbuf_free(fbuf); return ymod; /* top-level (sub)module */ } /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * @param[in] h CLICON handle - * @param[in] yang_dir Directory where all YANG module files reside - * @param[in] module Name of main YANG module. More modules may be parsed if imported - * @param[in] revision Optional module revision date + * @param[in] yang_dir Directory where all YANG module files reside (except mainfile) + * @param[in] mainmod Name of main YANG module. Or absolute file name. + * @param[in] revision Optional main module revision date. * @param[out] ysp Yang specification. Should ave been created by caller using yspec_new * @retval 0 Everything OK * @retval -1 Error encountered * The database symbols are inserted in alphabetical order. * Find a yang module file, and then recursively parse all its imported modules. + * @note if mainmod is filename, revision is not considered. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree - * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them - * yang_parse2 # Find file from yang (sub)module + * yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them * yang_parse_file # Read yang file into a string * yang_parse_str # Set up yacc parser and call it given a string * clixon_yang_parseparse # Actual yang parsing using yacc @@ -1571,7 +1520,7 @@ yang_parse1(clicon_handle h, int yang_parse(clicon_handle h, const char *yang_dir, - const char *module, + const char *mainmodule, const char *revision, yang_spec *ysp) { @@ -1579,7 +1528,7 @@ yang_parse(clicon_handle h, yang_stmt *ymod; /* Top-level yang (sub)module */ /* Step 1: parse from text to yang parse-tree. */ - if ((ymod = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL) + if ((ymod = yang_parse_recurse(h, yang_dir, mainmodule, revision, ysp)) == NULL) goto done; /* Add top module name as dbspec-name */ clicon_dbspec_name_set(h, ymod->ys_argument); @@ -1661,16 +1610,22 @@ yang_apply(yang_node *yn, return retval; } -/*! All the work for yang_xpath. - Ignore prefixes, see _abs */ +/*! All the work for schema_nodeid functions both absolute and descendant + * Ignore prefixes, see _abs + * @param[in] yn Yang node + * @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b + * @param[in] nvec Length of vec + * @retval NULL Error, with clicon_err called + * @retval yres First yang node matching schema nodeid + */ static yang_node * -yang_xpath_vec(yang_node *yn, - char **vec, - int nvec) +schema_nodeid_vec(yang_node *yn, + char **vec, + int nvec) { char *arg; yang_stmt *ys; - yang_node *yret = NULL; + yang_node *yres = NULL; char *id; if (nvec <= 0) @@ -1687,44 +1642,46 @@ yang_xpath_vec(yang_node *yn, id = arg; else id++; - if ((ys = yang_find_xpath_stmt(yn, id)) == NULL){ + if ((ys = schema_nodeid_stmt(yn, id)) == NULL){ clicon_debug(1, "%s %s not found", __FUNCTION__, id); goto done; } } if (nvec == 1){ - yret = (yang_node*)ys; + yres = (yang_node*)ys; goto done; } - yret = yang_xpath_vec((yang_node*)ys, vec+1, nvec-1); + yres = schema_nodeid_vec((yang_node*)ys, vec+1, nvec-1); done: - return yret; + return yres; } - -/*! Given an absolute xpath (eg /a/b/c) find matching yang specification - * @param[in] yn Yang node - * @param[in] xpath Absolute xpath, ie /a/b +/*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec + * @param[in] yn Yang node + * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b + * @retval NULL Error, with clicon_err called + * @retval yres First yang node matching schema nodeid + * @see yang_schema_nodeid */ yang_node * -yang_xpath_abs(yang_node *yn, - char *xpath) +yang_abs_schema_nodeid(yang_node *yn, + char *schema_nodeid) { char **vec = NULL; int nvec; - yang_node *ys = NULL; + yang_node *yres = NULL; yang_stmt *ymod; char *id; char *prefix = NULL; - if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL){ + if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL){ clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__); - return NULL; + goto done; } - /* Assume path looks like: "/prefix:id[/prefix:id]*" */ + /* Assume schame nodeid looks like: "/prefix:id[/prefix:id]*" */ if (nvec < 2){ clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s", - __FUNCTION__, xpath); + __FUNCTION__, schema_nodeid); goto done; } @@ -1753,53 +1710,77 @@ yang_xpath_abs(yang_node *yn, if ((ymod = yang_find_module_by_prefix((yang_stmt*)yn, prefix)) == NULL) goto done; } - ys = yang_xpath_vec((yang_node*)ymod, vec+1, nvec-1); + yres = schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1); done: if (vec) free(vec); if (prefix) free(prefix); - return ys; + return yres; } -/*! Given an xpath (eg /a/b/c or a/b/c) find matching yang specification - * Note that xpath is defined for xml, and for instances of data, this is - * for specifications, sp expect some differences. +/*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec + * @param[in] yn Yang node + * @param[in] schema_nodeid Descendant schema-node-id, ie a/b + * @retval NULL Error, with clicon_err called + * @retval yres First yang node matching schema nodeid + * @see yang_schema_nodeid + */ +yang_node * +yang_desc_schema_nodeid(yang_node *yn, + char *schema_nodeid) +{ + yang_node *yres = NULL; + char **vec = NULL; + int nvec; + + if (strlen(schema_nodeid) == 0) + goto done; + /* check absolute schema_nodeid */ + if (schema_nodeid[0] == '/'){ + clicon_err(OE_YANG, EINVAL, "descenadant schema nodeid should not start with /"); + goto done; + } + if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL) + goto done; + yres = schema_nodeid_vec((yang_node*)yn, vec, nvec); + done: + if (vec) + free(vec); + return yres; +} + +/*! Given a schema-node-id (eg /a/b/c or a/b/c) find matching yang specification + * + * They are either absolute (start with /) or descendant (no /) + * Compare with XPATH for XML, this is for YANG spec * @param[in] yn Yang node tree - * @param[in] xpath A limited xpath expression on the type a/b/c + * @param[in] schema_nodeid schema-node-id, ie a/b or /a/b * @retval NULL Error, with clicon_err called - * @retval ys First yang node matching xpath + * @retval yres First yang node matching schema nodeid * @note: the identifiers in the xpath (eg a, b in a/b) can match the nodes * defined in yang_xpath: container, leaf,list,leaf-list, modules, sub-modules * Example: * yn : module m { prefix b; container b { list c { key d; leaf d; }} } - * xpath = m/b/c, returns the list 'c'. - * @see xpath_vec - * @see clicon_dbget_xpath + * schema-node-id = m/b/c, returns the list 'c'. */ yang_node * -yang_xpath(yang_node *yn, - char *xpath) +yang_schema_nodeid(yang_node *yn, + char *schema_nodeid) { - char **vec = NULL; - yang_node *ys = NULL; - int nvec; + yang_node *yres = NULL; - if (strlen(xpath) == 0) - return NULL; - /* check absolute path */ - if (xpath[0] == '/') - return yang_xpath_abs(yn, xpath); - if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL) - goto err; - ys = yang_xpath_vec((yang_node*)yn, vec, nvec); - err: - if (vec) - free(vec); - return ys; + if (strlen(schema_nodeid) == 0) + goto done; + /* check absolute schema_nodeid */ + if (schema_nodeid[0] == '/') + yres = yang_abs_schema_nodeid(yn, schema_nodeid); + else + yres = yang_desc_schema_nodeid(yn, schema_nodeid); + done: + return yres; } - /*! Parse argument as CV and save result in yang cv variable * * Note that some CV:s are parsed directly (eg fraction-digits) while others are parsed @@ -1933,6 +1914,7 @@ yang_spec_main(clicon_handle h, clicon_err(OE_FATAL, 0, "CLICON_YANG_DIR option not set"); goto done; } + /* Yang module is either specific absolute filename, or main module */ if ((yang_module = clicon_yang_module_main(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_YANG_MODULE_MAIN option not set"); goto done; diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index 8364885a..813bd2d0 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -202,14 +202,12 @@ clixon_yang_parsewrap(void) . { clixon_yang_parselval.string = strdup(yytext); return CHAR;} - \\ { _YY->yy_lex_state = STRING1; BEGIN(ESCAPE); } \" { BEGIN(_YY->yy_lex_string_state); return DQ; } \n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} . { clixon_yang_parselval.string = strdup(yytext); return CHAR;} -\\ { _YY->yy_lex_state = STRING2; BEGIN(ESCAPE); } \' { BEGIN(_YY->yy_lex_string_state); return DQ; } \n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} . { clixon_yang_parselval.string = strdup(yytext); diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 79874b88..88ddb6fb 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -221,6 +221,10 @@ ys_resolve_type(yang_stmt *ys, if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, &options, &mincv, &maxcv, &pattern, &fraction) < 0) goto done; + /* skip unions since they may have different sets of options, mincv, etc */ + if (resolved && strcmp(resolved->ys_argument, "union")==0) + ; + else if (yang_type_cache_set(&ys->ys_typecache, resolved, options, mincv, maxcv, pattern, fraction) < 0) goto done; @@ -561,6 +565,91 @@ cv_validate1(cg_var *cv, return retval; } +/* Forward */ +static int ys_cv_validate_union(yang_stmt *ys, char **reason, yang_stmt *yrestype, + char *type, char *val); + +/*! + * @retval -1 Error (fatal), with errno set to indicate error + * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() + * @retval 1 Validation OK + */ +static int +ys_cv_validate_union_one(yang_stmt *ys, + char **reason, + yang_stmt *yt, + char *type, /* orig type */ + char *val) +{ + int retval = -1; + yang_stmt *yrt; /* union subtype */ + int options = 0; + cg_var *range_min = NULL; + cg_var *range_max = NULL; + char *pattern = NULL; + uint8_t fraction = 0; + char *restype; + enum cv_type cvtype; + cg_var *cvt=NULL; + + if (yang_type_resolve(ys, yt, &yrt, + &options, &range_min, &range_max, &pattern, + &fraction) < 0) + goto done; + restype = yrt?yrt->ys_argument:NULL; + if (restype && strcmp(restype, "union") == 0){ /* recursive union */ + if ((retval = ys_cv_validate_union(ys, reason, yrt, type, val)) < 0) + goto done; + } + else { + if (clicon_type2cv(type, restype, &cvtype) < 0) + goto done; + /* reparse value with the new type */ + if ((cvt = cv_new(cvtype)) == NULL){ + clicon_err(OE_UNIX, errno, "cv_new"); + goto done; + } + if (cv_parse(val, cvt) <0){ + clicon_err(OE_UNIX, errno, "cv_parse"); + goto done; + } + if ((retval = cv_validate1(cvt, cvtype, options, range_min, range_max, + pattern, yrt, restype, reason)) < 0) + goto done; + } + done: + if (cvt) + cv_free(cvt); + return retval; +} + +/*! + * @retval -1 Error (fatal), with errno set to indicate error + * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() + * @retval 1 Validation OK + */ +static int +ys_cv_validate_union(yang_stmt *ys, + char **reason, + yang_stmt *yrestype, + char *type, /* orig type */ + char *val) +{ + int retval = 1; /* valid */ + yang_stmt *yt = NULL; + + while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){ + if (yt->ys_keyword != Y_TYPE) + continue; + if ((retval = ys_cv_validate_union_one(ys, reason, yt, type, val)) < 0) + goto done; + if (retval == 1) /* Enough that one type validates value */ + break; + } + done: + return retval; +} + /*! Validate cligen variable cv using yang statement as spec * * @param[in] cv A cligen variable to validate. This is a correctly parsed cv. @@ -579,17 +668,15 @@ ys_cv_validate(cg_var *cv, { int retval = -1; cg_var *ycv; /* cv of yang-statement */ - int options; - cg_var *range_min; - cg_var *range_max; - char *pattern; + int options = 0; + cg_var *range_min = NULL; + cg_var *range_max = NULL; + char *pattern = NULL; enum cv_type cvtype; char *type; /* orig type */ yang_stmt *yrestype; /* resolved type */ - yang_stmt *yrt; /* union subtype */ char *restype; - uint8_t fraction; - yang_stmt *yt = NULL; + uint8_t fraction = 0; int retval2; char *val; cg_var *cvt=NULL; @@ -621,38 +708,10 @@ ys_cv_validate(cg_var *cv, } /* Note restype can be NULL here for example with unresolved hardcoded uuid */ if (restype && strcmp(restype, "union") == 0){ - yt = NULL; - retval2 = 1; /* valid */ assert(cvtype == CGV_REST); val = cv_string_get(cv); - while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){ - if (yt->ys_keyword != Y_TYPE) - continue; - if (yang_type_resolve(ys, yt, &yrt, - &options, &range_min, &range_max, &pattern, - &fraction) < 0) - goto done; - restype = yrestype?yrt->ys_argument:NULL; - if (clicon_type2cv(type, restype, &cvtype) < 0) - goto done; - /* reparse value with the new type */ - if ((cvt = cv_new(cvtype)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_new"); - goto done; - } - if (cv_parse(val, cvt) <0){ - clicon_err(OE_UNIX, errno, "cv_parse"); - goto done; - } - if ((retval2 = cv_validate1(cvt, cvtype, options, range_min, range_max, - pattern, yrt, restype, reason)) < 0) - goto done; - if (retval2 == 1) /* done */ - break; - /* Here retval == 0, validation failed */ - cv_free(cvt); - cvt=NULL; - } + if ((retval2 = ys_cv_validate_union(ys, reason, yrestype, type, val)) < 0) + goto done; retval = retval2; /* invalid (0) with latest reason or valid 1 */ } else @@ -860,7 +919,7 @@ yang_type_resolve(yang_stmt *ys, /* Not basic type. Now check if prefix which means we look in other module */ if (prefix){ /* Go to top and find import that matches */ if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){ - clicon_err(OE_DB, 0, "Module not resolved: %s", prefix); + clicon_err(OE_DB, 0, "Type not resolved: %s:%s", prefix, type); goto done; } if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL) diff --git a/test/lib.sh b/test/lib.sh index 074fb965..88c60af6 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -36,6 +36,9 @@ expectfn(){ if [ -z "$ret" -a -z "$expect" ]; then return fi + if [ -z "$ret" -a "$expect" = "^$" ]; then + return + fi # grep extended grep match=`echo "$ret" | grep -EZo "$expect"` # echo "ret:\"$ret\"" diff --git a/test/test_cli.sh b/test/test_cli.sh index 8931b045..de1674ff 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -29,19 +29,19 @@ fi new "cli tests" new "cli configure top" -expectfn "$clixon_cli -1f $clixon_cf set interfaces" "" +expectfn "$clixon_cli -1f $clixon_cf set interfaces" "^$" new "cli show configuration top (no presence)" -expectfn "$clixon_cli -1f $clixon_cf show conf cli" "" +expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^$" new "cli configure delete top" -expectfn "$clixon_cli -1f $clixon_cf delete interfaces" "" +expectfn "$clixon_cli -1f $clixon_cf delete interfaces" "^$" new "cli show configuration delete top" -expectfn "$clixon_cli -1f $clixon_cf show conf cli" "" +expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^$" new "cli configure" -expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0" "" +expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0" "^$" new "cli show configuration" expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" @@ -50,9 +50,9 @@ new "cli failed validate" expectfn "$clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable" new "cli configure more" -expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" "" -expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 description mydesc" "" -expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 type bgp" "" +expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" "^$" +expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 description mydesc" "^$" +expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 type bgp" "^$" new "cli show xpath description" expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "mydesc" @@ -61,32 +61,32 @@ new "cli delete description" expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth/0/0 description mydesc" new "cli show xpath no description" -expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "" +expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "^$" new "cli success validate" -expectfn "$clixon_cli -1f $clixon_cf -l o validate" "" +expectfn "$clixon_cli -1f $clixon_cf -l o validate" "^$" new "cli commit" -expectfn "$clixon_cli -1f $clixon_cf -l o commit" "" +expectfn "$clixon_cli -1f $clixon_cf -l o commit" "^$" new "cli save" -expectfn "$clixon_cli -1f $clixon_cf -l o save /tmp/foo" "" +expectfn "$clixon_cli -1f $clixon_cf -l o save /tmp/foo" "^$" new "cli delete all" -expectfn "$clixon_cli -1f $clixon_cf -l o delete all" "" +expectfn "$clixon_cli -1f $clixon_cf -l o delete all" "^$" new "cli load" -expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" "" +expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" "^$" new "cli check load" expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" new "cli debug" -expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" "" +expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" "^$" # How to test this? -expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" "" +expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" "^$" -new "cli downcall" +new "cli rpc" expectfn "$clixon_cli -1f $clixon_cf -l o rpc ipv4" "^" new "Kill backend" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 5a22c42a..6c74f070 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -48,47 +48,47 @@ EOF # kill old backend (if any) new "kill old backend" -sudo clixon_backend -zf $clixon_cf -y /tmp/leafref +sudo clixon_backend -zf $clixon_cf -y /tmp/leafref.yang if [ $? -ne 0 ]; then err fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/leafref +sudo clixon_backend -If $clixon_cf -y /tmp/leafref.yang if [ $? -ne 0 ]; then err fi new "leafref base config" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0up
192.0.2.1
192.0.2.2
loup
127.0.0.1
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "eth0up
192.0.2.1
192.0.2.2
loup
127.0.0.1
]]>]]>" "^]]>]]>$" new "leafref get config" expecteof "$clixon_netconf -qf $clixon_cf" ']]>]]>' '^eth0' new "leafref base commit" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "]]>]]>" "^]]>]]>$" new "leafref add wrong ref" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^missing-attribute" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "]]>]]>" "^missing-attribute" new "leafref discard-changes" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" new "leafref add correct ref" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0
192.0.2.2
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "eth0
192.0.2.2
]]>]]>" "^]]>]]>$" new "leafref validate (ok)" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "]]>]]>" "^" new "leafref delete leaf" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "eth0]]>]]>" "^" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "]]>]]>" "^missing-attribute" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref.yang" "]]>]]>" "^missing-attribute" new "Kill backend" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 0d5ff583..c1826ab8 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -68,6 +68,12 @@ expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" new "netconf get empty config2" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +new "netconf edit extra xml" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + new "netconf edit config eth1" expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^]]>]]>$" diff --git a/test/test_type.sh b/test/test_type.sh new file mode 100755 index 00000000..c46fcbc5 --- /dev/null +++ b/test/test_type.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Advanced union types and generated code + +# include err() and new() functions +. ./lib.sh + +# For memcheck +#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" +clixon_cli=clixon_cli +clixon_netconf=clixon_netconf + +cat < /tmp/type.yang +module example{ + typedef ab { + type string { + pattern + '(([a-b])\.){3}[a-b]'; + } + } + typedef cd { + type string { + pattern + '(([c-d])\.){3}[c-d]'; + } + } + typedef ef { + type string { + pattern + '(([e-f])\.){3}[e-f]'; + } + } + typedef ad { + type union { + type ab; + type cd; + } + } + typedef af { + type union { + type ad; + type ef; + } + } + list list { + key k; + leaf k { + type af; + } + } +} +EOF + + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $clixon_cf +if [ $? -ne 0 ]; then + err +fi +new "start backend" +# start new backend +sudo clixon_backend -If $clixon_cf -y /tmp/type.yang +if [ $? -ne 0 ]; then + err +fi + +new "cli set ab" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set list a.b.a.b" "^$" + +new "cli set cd" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set list c.d.c.d" "^$" + +new "cli set ef" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set list e.f.e.f" "^$" + +new "cli set ab fail" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set list a&b&a&b" "^CLI syntax error" + +new "cli set ad fail" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang set list a.b.c.d" "^CLI syntax error" + +new "cli validate" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang -l o validate" "^$" + +new "cli commit" +expectfn "$clixon_cli -1f $clixon_cf -l o -y /tmp/type.yang -l o commit" "^$" + +new "netconf validate ok" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + +new "netconf set ab fail" +expecteof "$clixon_netconf -qf $clixon_cf" "a.b&c.d]]>]]>" "^]]>]]>$" + +new "netconf validate" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + +new "netconf commit" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + +new "Kill backend" +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $clixon_cf +if [ $? -ne 0 ]; then + err "kill backend" +fi + diff --git a/test/test_yang.sh b/test/test_yang.sh index 784dd6f4..5f3e84d3 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -48,6 +48,9 @@ module example{ type string; } } + anyxml any{ + description "testing of anyxml"; + } } container state { config false; @@ -60,51 +63,66 @@ EOF # kill old backend (if any) new "kill old backend" -sudo clixon_backend -zf $clixon_cf -y /tmp/test +sudo clixon_backend -zf $clixon_cf -y /tmp/test.yang if [ $? -ne 0 ]; then err fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/test +sudo clixon_backend -If $clixon_cf -y /tmp/test.yang if [ $? -ne 0 ]; then err fi + new "netconf edit config" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "125]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "125]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" + +# text empty type in running +new "netconf commit 2nd" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^125]]>]]>$" new "netconf edit leaf-list" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "hejhopp]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "hejhopp]]>]]>" "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^hejhopp]]>]]>$" new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^125]]>]]>$" new "cli set leaf-list" -expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test set x f e foo" "" +expectfn "$clixon_cli -D 1 -1f $clixon_cf -y /tmp/test.yang set x f e foo" "" new "cli show leaf-list" -expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test show xpath /x/f/e" "foo" +expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test.yang show xpath /x/f/e" "foo" new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "42]]>]]>" "^invalid-value" new "netconf set presence and not present" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" new "netconf get" -expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + + +new "netconf anyxml" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" + +new "netconf validate anyxml" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test.yang" "]]>]]>" "^]]>]]>$" new "Kill backend" # Check if still alive