From ea77e7f02d0f82c73aacb5bfba8e48421d10787e Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 10 Oct 2018 20:11:20 +0200 Subject: [PATCH] * YANG Features * Yang 1.1 feature and if-feature according to RFC 7950 7.20.1 and 7.20.2. * See https://github.com/clicon/clixon/issues/41 * Features are declared via CLICON_FEATURE in the configuration file. * logical combination of features not implemented, eg if-feature "not foo or * Identity without any identityref:s caused SEGV --- CHANGELOG.md | 11 ++ README.md | 3 +- apps/backend/backend_main.c | 9 +- apps/backend/clixon_backend_handle.c | 2 +- apps/cli/cli_generate.c | 26 ++-- apps/cli/cli_main.c | 5 +- apps/netconf/netconf_main.c | 7 +- apps/restconf/restconf_main.c | 5 +- doc/FAQ.md | 13 ++ example/example.xml | 1 + lib/clixon/clixon_options.h | 3 + lib/clixon/clixon_yang.h | 1 - lib/src/clixon_handle.c | 11 +- lib/src/clixon_options.c | 102 ++++++++++---- lib/src/clixon_proto_client.c | 2 +- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_yang.c | 203 +++++++++++++++++++++++---- test/test_cli.sh | 1 - test/test_event.sh | 2 +- yang/clixon-config@2018-04-30.yang | 10 ++ 20 files changed, 332 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d7981c..cfa85e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ ## 3.8.0 (Upcoming) ### Major New features +* YANG Features + * Yang 1.1 feature and if-feature according to RFC 7950 7.20.1 and 7.20.2. + * Features are declared via CLICON_FEATURE in the configuration file. Examples showing enabling (1) a specific feature; (2) all features in a module; (3) all features in all modules: +``` + ietf-routing:router-id + ietf-routing:* + *:* +``` + * logical combination of features not implemented, eg if-feature "not foo or bar and baz"; * YANG Module Library support * According to RFC 7895 and implemented by ietf-yang-library.yang * Supported: module, name, revision, namespace @@ -44,11 +53,13 @@ ### Minor changes +* New function: clicon_conf_xml() returns configuration tree * Obsoleted COMPAT_CLIV and COMPAT_XSL that were optional in 3.7 * Added timeout option -t for clixon_netconf - quit after max time. * Added -l option for clixon_backend for directing syslog to stderr or stdout if running in foreground ### Corrected Bugs +* Identity without any identityref:s caused SEGV * Memory error in backend transaction revert * Set dir /www-data with www-data as owner, see https://github.com/clicon/clixon/issues/37 diff --git a/README.md b/README.md index e422bb35..b821d658 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,9 @@ used to generate an interactive CLI, netconf and restconf clients. It also manages an XML datastore. Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions: -- conformance: feature, if-feature, deviation +- conformance: deviation - list features: min/max-elements, unique - action statements -- notifications The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 43e0cdbb..2cc110f2 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -81,13 +81,16 @@ static int backend_terminate(clicon_handle h) { - yang_spec *yspec; - char *pidfile = clicon_backend_pidfile(h); - char *sockpath = clicon_sock(h); + yang_spec *yspec; + char *pidfile = clicon_backend_pidfile(h); + char *sockpath = clicon_sock(h); + cxobj *x; clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_conf_xml(h)) != NULL) + xml_free(x); clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ rpc_callback_delete_all(); diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index 98dbc3e3..4876fcbb 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -86,7 +86,7 @@ struct backend_handle { int bh_magic; /* magic (HDR)*/ clicon_hash_t *bh_copt; /* clicon option list (HDR) */ clicon_hash_t *bh_data; /* internal clicon data (HDR) */ - event_stream_t *ch_stream; /* notification streams, see clixon_stream.[ch] */ + event_stream_t *bh_stream; /* notification streams, see clixon_stream.[ch] */ /* ------ end of common handle ------ */ struct client_entry *bh_ce_list; /* The client list */ diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 4ffb55ca..22f76883 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -232,19 +232,21 @@ yang2cli_var_sub(clicon_handle h, cprintf(cb, ">"); if (helptext) cprintf(cb, "(\"%s\")", helptext); - cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr); if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL && (ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){ - i = 0; - while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){ - if (i++) - cprintf(cb, "|"); - name = strdup(cv_name_get(cv)); - if ((id=strchr(name, ':')) != NULL) - *id = '\0'; - cprintf(cb, "%s:%s", name, id+1); - if (name) - free(name); + if (cvec_len(ybaseid->ys_cvec) > 0){ + cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr); + i = 0; + while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){ + if (i++) + cprintf(cb, "|"); + name = strdup(cv_name_get(cv)); + if ((id=strchr(name, ':')) != NULL) + *id = '\0'; + cprintf(cb, "%s:%s", name, id+1); + if (name) + free(name); + } } } } @@ -792,7 +794,7 @@ yang2cli(clicon_handle h, if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0) goto done; } - clicon_debug(1, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf)); + clicon_debug(0, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf)); /* Parse the buffer using cligen parser. XXX why this?*/ if ((globals = cvec_new(0)) == NULL) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index f5881713..c63b6e98 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -79,11 +79,14 @@ static int cli_terminate(clicon_handle h) { - yang_spec *yspec; + yang_spec *yspec; + cxobj *x; clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_conf_xml(h)) != NULL) + xml_free(x); cli_plugin_finish(h); cli_handle_exit(h); clicon_log_exit(); diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index f34f572c..7617beac 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -269,13 +269,16 @@ send_hello(int s) static int netconf_terminate(clicon_handle h) { - yang_spec *yspec; - + yang_spec *yspec; + cxobj *x; + clixon_plugin_exit(h); rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_conf_xml(h)) != NULL) + xml_free(x); event_exit(); clicon_handle_exit(h); clicon_log_exit(); diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 3bd4584c..a38cf164 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -445,13 +445,16 @@ api_restconf(clicon_handle h, static int restconf_terminate(clicon_handle h) { - yang_spec *yspec; + yang_spec *yspec; + cxobj *x; clixon_plugin_exit(h); rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_conf_xml(h)) != NULL) + xml_free(x); clicon_handle_exit(h); clicon_log_exit(); return 0; diff --git a/doc/FAQ.md b/doc/FAQ.md index 2d1800b3..11e68214 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -100,6 +100,19 @@ You can change where CLixon looks for the configuration FILE as follows: - Provide --sysconfig= when configuring then FILE is /etc/clixon.xml - FILE is /usr/local/etc/clixon.xml +## How do I enable Yang features? + +Yang models have features, and parts of a specification can be +conditional using the if-feature statement. In Clixon, features are +enabled in the configuration file using . + +The example below shows enabling a specific feature; enabling all features in module; and enabling all features in all modules, respectively: +``` + ietf-routing:router-id + ietf-routing:* + *:* +``` + ## Can I run Clixon as docker containers? Yes, the example works as docker containers as well. There should be a diff --git a/example/example.xml b/example/example.xml index 337048bb..c222f6ce 100644 --- a/example/example.xml +++ b/example/example.xml @@ -1,5 +1,6 @@ /usr/local/etc/example.xml + *:* /usr/local/share/example/yang example example diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 738b2249..3eef5507 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -164,6 +164,9 @@ int clicon_quiet_mode_set(clicon_handle h, int val); yang_spec * clicon_dbspec_yang(clicon_handle h); int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys); +cxobj *clicon_conf_xml(clicon_handle h); +int clicon_conf_xml_set(clicon_handle h, cxobj *x); + plghndl_t clicon_xmldb_plugin_get(clicon_handle h); int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 465fa1a6..3996b806 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -259,7 +259,6 @@ char *yang_find_myprefix(yang_stmt *ys); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); -int ys_populate(yang_stmt *ys, void *arg); yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp); int yang_parse(clicon_handle h, const char *filename, const char *module, const char *dir, diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index 940d7cf6..64d58628 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -129,13 +129,12 @@ int clicon_handle_exit(clicon_handle h) { struct clicon_handle *ch = handle(h); - clicon_hash_t *copt; - clicon_hash_t *data; + clicon_hash_t *ha; - if ((copt = clicon_options(h)) != NULL) - hash_free(copt); - if ((data = clicon_data(h)) != NULL) - hash_free(data); + if ((ha = clicon_options(h)) != NULL) + hash_free(ha); + if ((ha = clicon_data(h)) != NULL) + hash_free(ha); stream_delete_all(clicon_stream(h)); free(ch); return 0; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index d44f4df6..2070cda2 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -64,8 +64,8 @@ #include "clixon_handle.h" #include "clixon_log.h" #include "clixon_yang.h" -#include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_options.h" #include "clixon_plugin.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" @@ -116,12 +116,16 @@ clicon_option_dump(clicon_handle h, } /*! Read filename and set values to global options registry. XML variant. - * @see clicon_option_readfile + * + * @param[out] xconfig Pointer to xml config tree. Should be freed by caller + * @retval 0 OK + * @retval -1 Error */ -static int -clicon_option_readfile_xml(clicon_hash_t *copt, - const char *filename, - yang_spec *yspec) +static int +parse_configfile(clicon_handle h, + const char *filename, + yang_spec *yspec, + cxobj **xconfig) { struct stat st; FILE *f = NULL; @@ -132,6 +136,7 @@ clicon_option_readfile_xml(clicon_hash_t *copt, cxobj *x = NULL; char *name; char *body; + clicon_hash_t *copt = clicon_options(h); if (filename == NULL || !strlen(filename)){ clicon_err(OE_UNIX, 0, "Not specified"); @@ -168,14 +173,22 @@ clicon_option_readfile_xml(clicon_hash_t *copt, while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { name = xml_name(x); body = xml_body(x); - if (name && body && - (hash_add(copt, - name, - body, - strlen(body)+1)) == NULL) + if (name==NULL || body == NULL){ + clicon_log(LOG_WARNING, "%s option NULL: name:%s body:%s", + __FUNCTION__, name, body); + continue; + } + if (strcmp(name,"CLICON_FEATURE")==0) + continue; + if (hash_add(copt, + name, + body, + strlen(body)+1) == NULL) goto done; } retval = 0; + *xconfig = xt; + xt = NULL; done: if (xt) xml_free(xt); @@ -199,6 +212,7 @@ clicon_options_main(clicon_handle h) char *suffix; char xml = 0; /* Configfile is xml, otherwise legacy */ yang_spec *yspec = NULL; + cxobj *xconfig = NULL; /* * Set configure file if not set by command-line above @@ -213,29 +227,30 @@ clicon_options_main(clicon_handle h) suffix++; xml = strcmp(suffix, "xml") == 0; } - if (xml){ /* Read clixon yang file */ - if ((yspec = yspec_new()) == NULL) - goto done; - if (yang_parse(h, NULL, "clixon-config", CLIXON_DATADIR, NULL, yspec) < 0) - goto done; - /* Read configfile */ - if (clicon_option_readfile_xml(copt, configfile, yspec) < 0) - goto done; - /* Specific option handling */ - if (clicon_option_bool(h, "CLICON_XML_SORT") == 1) - xml_child_sort = 1; - else - xml_child_sort = 0; - } - else { + if (xml == 0){ clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } + /* Parse clixon yang spec */ + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse(h, NULL, "clixon-config", CLIXON_DATADIR, NULL, yspec) < 0) + goto done; + /* Read configfile */ + if (parse_configfile(h, configfile, yspec, &xconfig) < 0) + goto done; + if (xml_rootchild(xconfig, 0, &xconfig) < 0) + goto done; + clicon_conf_xml_set(h, xconfig); + /* Specific option handling */ + if (clicon_option_bool(h, "CLICON_XML_SORT") == 1) + xml_child_sort = 1; + else + xml_child_sort = 0; retval = 0; done: if (yspec) /* The clixon yang-spec is not used after this */ yspec_free(yspec); - return retval; } @@ -571,6 +586,38 @@ clicon_dbspec_yang_set(clicon_handle h, return 0; } +/*! Get YANG specification for Clixon system options and features + * Must use hash functions directly since they are not strings. + */ +cxobj * +clicon_conf_xml(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "clixon_conf", &len)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set YANG specification for Clixon system options and features + * ys must be a malloced pointer + */ +int +clicon_conf_xml_set(clicon_handle h, + cxobj *x) +{ + clicon_hash_t *cdat = clicon_data(h); + + /* It is the pointer to x that should be copied by hash, + * so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL) + return -1; + return 0; +} + /*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */ plghndl_t clicon_xmldb_plugin_get(clicon_handle h) @@ -664,7 +711,6 @@ clicon_xmldb_handle_set(clicon_handle h, return 0; } - /*! Get authorized user name * @param[in] h Clicon handle * @retval xh XMLDB storage handle. If not connected return NULL diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index f5246109..2798e42b 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -61,8 +61,8 @@ #include "clixon_handle.h" #include "clixon_log.h" #include "clixon_yang.h" -#include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_options.h" #include "clixon_plugin.h" #include "clixon_string.h" #include "clixon_xpath_ctx.h" diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 4576fc82..ce9716b2 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -80,8 +80,8 @@ #include "clixon_string.h" #include "clixon_yang.h" #include "clixon_yang_type.h" -#include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_options.h" #include "clixon_plugin.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 8adbfdc1..e8c30ede 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -672,6 +672,10 @@ yang_find_topnode(yang_spec *ysp, * @param[in] ys Yang statement * @retval NULL Not found * @retval prefix Prefix as char* pointer into yang tree + * @code + * char *myprefix; + * myprefix = yang_find_myprefix(ys); + * @endcode */ char * yang_find_myprefix(yang_stmt *ys) @@ -862,6 +866,15 @@ yarg_prefix(yang_stmt *ys) * @param[out] id Malloced identifier. * @retval 0 OK * @retval -1 Error + * @code + * char *prefix = NULL; + * char *id = NULL; + * if (yang_nodeid_split(nodeid, &prefix, &id) < 0) + * goto done; + * if (prefix) + * free(prefix); + * if (id) + * free(id); * @note caller need to free id and prefix after use */ int @@ -1284,11 +1297,11 @@ ys_populate_type(yang_stmt *ys, * Do this recursively * @param[in] ys The yang identity to populate. * @param[in] arg If set contains a derived identifier - * @see validate_identityref which in runtime validates actual valoues + * @see validate_identityref which in runtime validates actual values */ static int ys_populate_identity(yang_stmt *ys, - void *arg) + char *idref) { int retval = -1; yang_stmt *yc = NULL; @@ -1298,7 +1311,6 @@ ys_populate_identity(yang_stmt *ys, char *baseid; char *prefix = NULL; cbuf *cb = NULL; - char *idref = (char*)arg; char *p; if (idref == NULL){ @@ -1358,31 +1370,91 @@ ys_populate_identity(yang_stmt *ys, return retval; } +/*! Populate yang feature statement - set cv to 1 if enabled + * + * @param[in] ys Feature yang statement to populate. + * @param[in] h Clicon handle + */ +static int +ys_populate_feature(clicon_handle h, + yang_stmt *ys) +{ + int retval = -1; + cxobj *x; + yang_stmt *ymod; + int found = 0; + cg_var *cv; + char *module; + char *feature; + cxobj *x1; + + /* Eg, when parsing the config xml itself */ + if ((x = clicon_conf_xml(h)) == NULL) + goto ok; + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "module not found"); + goto done; + } + module = ymod->ys_argument; + feature = ys->ys_argument; + x1 = NULL; + while ((x1 = xml_child_each(x, x1, CX_ELMNT)) != NULL && found == 0) { + char *m = NULL; + char *f = NULL; + if (strcmp(xml_name(x1), "CLICON_FEATURE") != 0) + continue; + /* get m and f from configuration feature rules */ + if (yang_nodeid_split(xml_body(x1), &m, &f) < 0) + goto done; + if (m && f && + (strcmp(m,"*")==0 || + strcmp(m, module)==0) && + (strcmp(f,"*")==0 || + strcmp(f, feature)==0)) + found = 1; + if (m) free(m); + if (f) free(f); + } + if ((cv = cv_new(CGV_BOOL)) == NULL){ + clicon_err(OE_YANG, errno, "cv_new"); + goto done; + } + cv_name_set(cv, feature); + cv_bool_set(cv, found); + clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found); + ys->ys_cv = cv; + ok: + retval = 0; + done: + return retval; +} + /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * * We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree * See ys_parse_sub for first pass and what can be assumed * After this pass, cv:s are set for LEAFs and LEAF-LISTs */ -int +static int ys_populate(yang_stmt *ys, void *arg) { int retval = -1; - + // clicon_handle h = (clicon_handle)arg; + switch(ys->ys_keyword){ case Y_LEAF: case Y_LEAF_LIST: - if (ys_populate_leaf(ys, arg) < 0) + if (ys_populate_leaf(ys, NULL) < 0) goto done; break; case Y_LIST: - if (ys_populate_list(ys, arg) < 0) + if (ys_populate_list(ys, NULL) < 0) goto done; break; case Y_RANGE: case Y_LENGTH: - if (ys_populate_range(ys, arg) < 0) + if (ys_populate_range(ys, NULL) < 0) goto done; break; case Y_MANDATORY: /* call yang_mandatory() to check if set */ @@ -1391,11 +1463,11 @@ ys_populate(yang_stmt *ys, goto done; break; case Y_TYPE: - if (ys_populate_type(ys, arg) < 0) + if (ys_populate_type(ys, NULL) < 0) goto done; break; case Y_IDENTITY: - if (ys_populate_identity(ys, arg) < 0) + if (ys_populate_identity(ys, NULL) < 0) goto done; break; default: @@ -1406,7 +1478,6 @@ ys_populate(yang_stmt *ys, return retval; } - /*! Resolve a grouping name from a point in the yang tree * @retval 0 OK, but ygrouping determines if a grouping was resolved or not * @retval -1 Error, with clicon_err called @@ -1852,6 +1923,8 @@ yang_parse_module(const char *module, if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL) goto done; done: + if (fbuf) + cbuf_free(fbuf); return ymod; /* top-level (sub)module */ } @@ -1894,7 +1967,7 @@ yang_parse_recurse(yang_stmt *ymod, subrevision = yrev->ys_argument; else subrevision = NULL; - if (yang_find((yang_node*)ysp, Y_MODULE, submodule) == NULL) + if (yang_find((yang_node*)ysp, Y_MODULE, submodule) == NULL){ /* recursive call */ if ((subymod = yang_parse_module(submodule, dir, subrevision, ysp)) == NULL) goto done; @@ -1902,6 +1975,7 @@ yang_parse_recurse(yang_stmt *ymod, ymod = NULL; goto done; } + } } retval = 0; done: @@ -1951,6 +2025,83 @@ ys_schemanode_check(yang_stmt *ys, return retval; } +/*! Find feature and if-feature nodes, check features and remove disabled nodes + * @retval -1 Error + * @retval 0 Feature not enabled: remove yt + * @retval 1 OK + * @note On return 1 the over-lying function need to remove yt from its parent + * @note cannot use yang_apply here since child-list is modified (destructive) + */ +static int +yang_features(clicon_handle h, + yang_stmt *yt) +{ + int retval = -1; + int i; + int j; + yang_stmt *ys = NULL; + char *prefix = NULL; + char *feature = NULL; + yang_stmt *ymod; /* module yang node */ + yang_stmt *yfeat; /* feature yang node */ + + i = 0; + while (iys_len){ /* Note, children may be removed */ + ys = yt->ys_stmt[i]; + if (ys->ys_keyword == Y_IF_FEATURE){ + if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0) + goto done; + /* Specifically need to handle? strcmp(prefix, myprefix)) */ + if (prefix == NULL) + ymod = ys_module(ys); + else + ymod = yang_find_module_by_prefix(yt, prefix); + + /* Check if feature exists, and is set, otherwise remove */ + if ((yfeat = yang_find((yang_node*)ymod, Y_FEATURE, feature)) == NULL || + yfeat->ys_cv == NULL || !cv_bool_get(yfeat->ys_cv)){ + retval = 0; /* feature not enabled */ + goto done; + } + if (prefix){ + free(prefix); + prefix = NULL; + } + if (feature){ + free(feature); + feature = NULL; + } + } + else + if (ys->ys_keyword == Y_FEATURE){ + if (ys_populate_feature(h, ys) < 0) + goto done; + } else switch (yang_features(h, ys)){ + case -1: /* error */ + goto done; + break; + case 0: /* disabled: remove ys */ + for (j=i+1; jys_len; j++) + yt->ys_stmt[j-1] = yt->ys_stmt[j]; + yt->ys_len--; + yt->ys_stmt[yt->ys_len] = NULL; + ys_free(ys); + continue; /* Don't increment i */ + break; + default: /* ok */ + break; + } + i++; + } + retval = 1; + done: + if (prefix) + free(prefix); + if (feature) + free(feature); + return retval; +} + /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * @param[in] h CLICON handle @@ -2001,12 +2152,18 @@ yang_parse(clicon_handle h, if (yang_parse_recurse(ymod, dir, ysp) < 0) goto done; - /* Step 2: Go through parse tree and populate it with cv types */ + /* Step 2: check features: check if enabled and remove disabled features */ + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_features(h, ysp->yp_stmt[i]) < 0) + goto done; + + /* Step 3: Go through parse tree and populate it with cv types */ for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, NULL) < 0) + if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0) goto done; - /* Step 3: Resolve all types: populate type caches. Requires eg length/range cvecs + + /* Step 4: Resolve all types: populate type caches. Requires eg length/range cvecs * from ys_populate step */ for (i=modnr; iyp_len; i++) @@ -2017,18 +2174,18 @@ yang_parse(clicon_handle h, grouping/uses and unfold all macros into a single tree as they are used. */ - /* Step 4: Macro expansion of all grouping/uses pairs. Expansion needs marking */ + /* Step 5: Macro expansion of all grouping/uses pairs. Expansion needs marking */ for (i=modnr; iyp_len; i++){ if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0) goto done; yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); } - /* Step 4: Top-level augmentation of all modules XXX: only new modules? */ + /* Step 6: Top-level augmentation of all modules XXX: only new modules? */ if (yang_augment_spec(ysp) < 0) goto done; - /* sanity check of schemanode references, need more here */ + /* Step 7: sanity check of schemanode references, need more here */ for (i=modnr; iyp_len; i++) if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) goto done; @@ -2486,11 +2643,8 @@ yang_spec_parse_module(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang dir not set"); goto done; } - if (yang_parse(h, NULL, module, dir, revision, yspec) < 0){ - yspec_free(yspec); - yspec = NULL; + if (yang_parse(h, NULL, module, dir, revision, yspec) < 0) goto done; - } retval = 0; done: return retval; @@ -2521,11 +2675,8 @@ yang_spec_parse_file(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang dir not set"); goto done; } - if (yang_parse(h, filename, NULL, dir, NULL, yspec) < 0){ - yspec_free(yspec); - yspec = NULL; + if (yang_parse(h, filename, NULL, dir, NULL, yspec) < 0) goto done; - } retval = 0; done: return retval; diff --git a/test/test_cli.sh b/test/test_cli.sh index 716fb9d3..5b2574bf 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -42,7 +42,6 @@ sudo $clixon_backend -s init -f $cfg if [ $? -ne 0 ]; then err fi -new "cli tests" new "cli configure top" expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$" diff --git a/test/test_event.sh b/test/test_event.sh index 61c198b7..ce403bc7 100755 --- a/test/test_event.sh +++ b/test/test_event.sh @@ -131,4 +131,4 @@ if [ -n "$pid" ]; then sudo kill $pid fi -#rm -rf $dir +rm -rf $dir diff --git a/yang/clixon-config@2018-04-30.yang b/yang/clixon-config@2018-04-30.yang index 676276d1..1535a156 100644 --- a/yang/clixon-config@2018-04-30.yang +++ b/yang/clixon-config@2018-04-30.yang @@ -117,6 +117,16 @@ module clixon-config { } } container config { + leaf-list CLICON_FEATURE { + description + "Supported features as used by YANG feature/if-feature + value is: :, where and + are either names, or the special character '*'. + *:an* means enable all features + :* means enable all features in the specified module + *: means enable the specific feature in all modules"; + type string; + } leaf CLICON_CONFIGFILE{ type string; description