diff --git a/CHANGELOG.md b/CHANGELOG.md index 919d820f..1215d15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,15 +33,37 @@ ## 5.3.0 Expected: September, 2021 -### C/CLI-API changes on existing features +### New features -Developers may need to change their code +* Restconf YANG PATCH according to RFC 8072 + * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h + * Thanks to Alan Yaniger for providing this patch + +### API changes on existing protocol/config features + +Users may have to change how they access the system * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` +* New clixon-config@2021-07-11.yang revision + * Removed default of `CLICON_RESTCONF_INSTALLDIR` + * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally + +### Minor features + +* Added linenumbers to all YANG symbols for better debug and errors + * Improved error messages for YANG identityref:s and leafref:s by adding original line numbers ### Corrected Bugs +* Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) +* Fixed: Yang patterns: \n and other non-printable characters were broken + * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 +* Fixed: The auto-cli identityref did not expand identities in grouping/usecases properly. +* Fixed: [OpenConfig BGP afi-safi and when condition issues #249](https://github.com/clicon/clixon/issues/249) + * YANG when was not properly implemented for default values +* Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. + * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists * Added YANG_FLAG_NOKEY as exception to mandatory key lists * Fixed: mandatory leaf in a uses statement caused abort diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index aecf95c7..4c55d23f 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -152,7 +152,9 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< + # Note: CLIXON_CONFIG_SBINDIR is where clixon_restconf is believed to be installed, unless + # overruled by CLICON_RESTCONF_INSTALLDIR option + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CONFIG_SBINDIR=\"$(sbindir)\" $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6244d200..be7b9377 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -238,7 +238,7 @@ client_get_streams(clicon_handle h, cprintf(cb,"", top); if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 03e318cf..8abcbcbc 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -132,7 +132,7 @@ generic_validate(clicon_handle h, cprintf(cb, "Mandatory variable of %s in module %s", xml_parent(x1)?xml_name(xml_parent(x1)):"", yang_argument_get(ys_module(ys))); - if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -480,9 +480,9 @@ startup_commit(clicon_handle h, * and call application callback validations. * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @param[out] xret Error XML tree. Free with xml_free after use + * @param[out] xret Error XML tree, if retval is 0. Free with xml_free after use * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) + * @retval 0 Validation failed (with xret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail * (only done for generic_validate) @@ -505,16 +505,19 @@ validate_common(clicon_handle h, goto done; } /* This is the state we are going to */ - if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0) goto done; - + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 2. Parse xml trees * This is the state we are going from */ - if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0) goto done; + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); @@ -606,11 +609,23 @@ candidate_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if ((ret = validate_common(h, db, td, &xret)) < 1){ + if ((ret = validate_common(h, db, td, &xret)) < 0){ /* A little complex due to several sources of validation fails or errors. * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise - * use clicon_err. */ - if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + * use clicon_err. + * TODO: -1 return should be fatal error, not failed validation + */ + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; + } + if (ret == 0){ + if (xret == NULL){ + clicon_err(OE_CFG, EINVAL, "xret is NULL"); + goto done; + } + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; if (!cbuf_len(cbret) && netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 6b82417d..fe39c8c5 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -245,6 +245,7 @@ restconf_pseudo_process_control(clicon_handle h) int i; int nr; cbuf *cb = NULL; + char *dir = NULL; nr = 10; if ((argv = calloc(nr, sizeof(char *))) == NULL){ @@ -256,12 +257,18 @@ restconf_pseudo_process_control(clicon_handle h) clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - /* CLICON_RESTCONF_INSTALLDIR is where we think clixon_restconf is installed - * Problem is where to define it? Now in config file, but maybe it should be in configure? - * Tried Makefile but didnt work on Docker since it was moved around. + /* Try to figure out where clixon_restconf is installed + * If config option CLICON_RESTCONF_INSTALLDIR is installed, use that. + * If not, use the Makefile * Use PATH? */ - cprintf(cb, "%s/clixon_restconf", clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")); + if ((dir = clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")) == NULL){ + if ((dir = CLIXON_CONFIG_SBINDIR) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "Both option CLICON_RESTCONF_INSTALLDIR and makefile constant CLIXON_CONFIG_SBINDIR are NULL which make sit not possible to know where clixon_restconf is installed(shouldnt happen)"); + goto done; + } + } + cprintf(cb, "%s/clixon_restconf", dir); argv[i++] = cbuf_get(cb); argv[i++] = "-f"; argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 2a3c74a9..4f1c6fdb 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -448,7 +448,7 @@ cli_debug_cli(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -479,7 +479,7 @@ cli_debug_backend(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -513,7 +513,7 @@ cli_debug_restconf(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 701288cc..2f37a5aa 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -184,7 +184,7 @@ yang2cli_helptext(cbuf *cb, /*! Generate identityref statements for CLI variables * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[out] cb Buffer where cligen code is written * @see yang2cli_var_sub Its sub-function @@ -208,42 +208,44 @@ yang2cli_var_identityref(yang_stmt *ys, yang_stmt *yprefix; yang_stmt *yspec; - if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) != NULL && - (ybaseid = yang_find_identity(ys, yang_argument_get(ybaseref))) != NULL){ - idrefvec = yang_cvec_get(ybaseid); - if (cvec_len(idrefvec) > 0){ - /* Add a wildchar string first -let validate take it for default prefix */ - cprintf(cb, ">"); - yang2cli_helptext(cb, helptext); - cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); - yspec = ys_spec(ys); - i = 0; - while ((cv = cvec_each(idrefvec, cv)) != NULL){ - if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) - goto done; - /* Translate from module-name(prefix) to global prefix - * This is really a kludge for true identityref prefix handling - * IDENTITYREF_KLUDGE - * This is actually quite complicated: the cli needs to generate - * a netconf statement with correct xmlns binding - */ - if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && - (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ - if (i++) - cprintf(cb, "|"); - cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); - } - if (prefix){ - free(prefix); - prefix = NULL; - } - if (id){ - free(id); - id = NULL; - } + if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL) + goto ok; + if ((ybaseid = yang_find_identity(ytype, yang_argument_get(ybaseref))) == NULL) + goto ok; + idrefvec = yang_cvec_get(ybaseid); + if (cvec_len(idrefvec) > 0){ + /* Add a wildchar string first -let validate take it for default prefix */ + cprintf(cb, ">"); + yang2cli_helptext(cb, helptext); + cprintf(cb, "|<%s:%s choice:", yang_argument_get(ys), cvtypestr); + yspec = ys_spec(ys); + i = 0; + while ((cv = cvec_each(idrefvec, cv)) != NULL){ + if (nodeid_split(cv_name_get(cv), &prefix, &id) < 0) + goto done; + /* Translate from module-name(prefix) to global prefix + * This is really a kludge for true identityref prefix handling + * IDENTITYREF_KLUDGE + * This is actually quite complicated: the cli needs to generate + * a netconf statement with correct xmlns binding + */ + if ((ymod = yang_find_module_by_name(yspec, prefix)) != NULL && + (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL){ + if (i++) + cprintf(cb, "|"); + cprintf(cb, "%s:%s", yang_argument_get(yprefix), id); + } + if (prefix){ + free(prefix); + prefix = NULL; + } + if (id){ + free(id); + id = NULL; } } } + ok: retval = 0; done: if (prefix) @@ -371,7 +373,7 @@ static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, * patterns, (eg regexp:"[0.9]*"). * @param[in] h Clixon handle * @param[in] ys Yang statement - * @param[in] ytype Yang union type being resolved + * @param[in] ytype Resolved yang type. * @param[in] helptext CLI help text * @param[in] cvtype * @param[in] options Flags field of optional values, see YANG_OPTIONS_* diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 436d719a..8ad952cf 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -176,31 +176,30 @@ netconf_get_config(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) { + goto done; + } + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if (nsc) @@ -388,31 +387,29 @@ netconf_get(clicon_handle h, } /* ie ... */ - if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) - ftype = xml_find_value(xfilter, "type"); - if (xfilter == NULL || ftype == NULL || strcmp(ftype, "xpath")==0){ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } - else if (strcmp(ftype, "subtree")==0){ - /* Get whole config + state first, then filter. This is suboptimal - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - /* Now filter on whole tree */ - if (netconf_get_config_subtree(h, xfilter, xret) < 0) - goto done; - } - else{ - clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" - "operation-failed" - "applicatio" - "error" - "filter type not supported" - "type" - "", - NETCONF_BASE_NAMESPACE); - } + if ((xfilter = xpath_first(xn, nsc, "%s%sfilter", prefix ? prefix : "", prefix ? ":" : "")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (xfilter == NULL || ftype == NULL || strcmp(ftype, "subtree") == 0) { + /* Get whole config + state first, then filter. This is suboptimal + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + /* Now filter on whole tree */ + if (netconf_get_config_subtree(h, xfilter, xret) < 0) + goto done; + } else if (strcmp(ftype, "xpath") == 0) { + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + } else { + clixon_xml_parse_va(YB_NONE, NULL, xret, NULL, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + "", + NETCONF_BASE_NAMESPACE); + } retval = 0; done: if(nsc) diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index dfb43597..44983de8 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -529,6 +529,7 @@ restconf_path_root(evhtp_request_t *req, clicon_err(OE_CFG, errno, "evbuffer_pullup"); goto done; } + cbuf_reset(sd->sd_indata); /* Note the pullup may not be null-terminated */ cbuf_append_buf(sd->sd_indata, buf, len); } diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 88622b6a..b8508102 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -419,6 +419,12 @@ main(int argc, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 29e65b7d..80a5a7a8 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1737,6 +1737,12 @@ restconf_clixon_init(clicon_handle h, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 10844869..fff0275c 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -65,6 +65,8 @@ /* cligen */ #include +// TODO - remove this include if cbuf_trunc() is added to cligen repo +#include "../cligen/cligen_buf_internal.h" /* clicon */ #include @@ -74,6 +76,7 @@ #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods.h" +#include "restconf_methods_post.h" /*! REST OPTIONS method * According to restconf @@ -579,6 +582,710 @@ api_data_write(clicon_handle h, return retval; } /* api_data_write */ +#ifdef YANG_PATCH + +/*! Free memory after a NULL pointer check + * + * @param [in] str void pointer to memory to be freed + * + */ +static void yang_patch_free_mem(void *p) +{ + if (p != NULL) + free(p); +} + +/*! Return a value within XML tags + * @param [in] nsc namespace context + * @param [in] xn cxobj containing XML with the current edit + * @param [in] val cbuf to which the value will be written + * @param [in] key string containing the tag + * @retval 0 success + * @retval <0 failure + */ +static int yang_patch_get_xval( + cvec* nsc, + cxobj* xn, + cbuf* val, + const char* key + ) +{ + cxobj **vec = NULL; + size_t veclen = 0; + char* tmp_val = NULL; + int ret = xpath_vec(xn, nsc, "%s", &vec, &veclen, key); + if (ret < 0) { + return ret; + } + cxobj *xn_tmp = NULL; + if (veclen == 1) { //veclen should always be 1 + xn_tmp = vec[0]; + } + if (xn_tmp != NULL) { + tmp_val = xml_body(xn_tmp); + cbuf_append_str(val, tmp_val); + } + return 0; +} + +// TODO - add this to cligen repo if it is approved +/*! Truncate a cbuf + * + * @param [in] cb cligen buffer allocated by cbuf_new(), may be reallocated. + * @param [in] int pos position at which to truncate + * @retval new cbuf containing the truncated string (old buffer remains as it was) + * @retval NULL Error + */ +cbuf* +cbuf_trunc(cbuf *cb, + int pos) +{ + if (pos < 0 || pos > cb->cb_strlen){ + errno = EINVAL; + return NULL; + } + /* Ensure buffer is right size */ + cbuf* new_buf = cbuf_new_alloc(pos + 1); + if (new_buf == NULL) + return NULL; + strncpy(new_buf->cb_buffer, cb->cb_buffer, pos); + new_buf->cb_strlen = pos; + return new_buf; +} + +/*! Add square brackets after the surrounding curly brackets in JSON + * Needed, in order to modify the result of xml2json_cbuf() to be valid input + * to api_dta_post() and api_dta_write() + * @param [in] x_simple_patch a cxobj to pass to xml2json_cbuf() + * @retval new cbuf with the modified json + * @retval NULL Error + */ + +static cbuf* yang_patch_xml2json_modified_cbuf(cxobj* x_simple_patch) +{ + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return NULL; + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Insert a '[' after the first '{' to get the JSON to match what api_data_post/write() expect + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { // We've reached the second brace, insert a '[' before it + cbuf_append(json_simple_patch,(int)'['); + } + } + cbuf_append(json_simple_patch,(int)c); + } + cbuf* json_simple_patch_2 = NULL; + + // Insert a ']' before the last '}' to get the JSON to match what api_data_post() expects + for (int l = cbuf_len(json_simple_patch) - 1; l >= 0; l--) { + char c = cbuf_get(json_simple_patch)[l]; + if (c == '}') { + // Truncate and add a string, as there is not a function to insert a char into a cbuf + json_simple_patch_2 = cbuf_trunc(json_simple_patch, l); + cbuf_append_str(json_simple_patch_2, "]}"); + break; + } + } + cbuf_free(json_simple_patch); + cbuf_free(cb); + return json_simple_patch_2; +} + +/*!yang_patch_strip_after_last_slash + * + * Strip /... from end of val + * so that e.g. "/interface=eth2" becomes "/" + * or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + * + * @param[in] val value to strip + * @retval new cbuf with the stripped string + * @retval NULL error + */ +static cbuf* yang_patch_strip_after_last_slash(cbuf* val) +{ + cbuf *cb = cbuf_new(); + cbuf* val_tmp = cbuf_new(); + cbuf_append_str(val_tmp, cbuf_get(val)); + int idx = cbuf_len(val_tmp); + for (int l = cbuf_len(val_tmp) - 1; l>= 0; l--) { + if (cbuf_get(val_tmp)[l] == '/') { + idx = l; + break; + } + } + if (idx == cbuf_len(val_tmp)) // Didn't find a slash in the loop above + return NULL; + cbuf* val_tmp_2 = cbuf_trunc(val_tmp, idx + 1); + if (cbuf_append_str(cb, cbuf_get(val_tmp_2)) < 0) + return NULL; + cbuf_free(val_tmp); + cbuf_free(val_tmp_2); + return cb; +} + +/*! YANG PATCH replace method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] target_val value in "target" field of edit in YANG patch + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_replace ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + cbuf* simple_patch_request_uri, + cbuf* target_val, + int value_vec_len, + cxobj** value_vec, + cxobj *x_simple_patch + ) +{ + cxobj * value_vec_tmp = NULL; + cbuf* delete_req_uri = cbuf_new(); + if (delete_req_uri == NULL) + return 1; + + // Make delete_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(delete_req_uri, cbuf_get(simple_patch_request_uri)) < 0) + return 1; + + // Add the target to delete_req_uri, + // so it's something like "/restconf/data/ietf-interfaces:interfaces/interface=eth2" + if (cbuf_append_str(delete_req_uri, cbuf_get(target_val)) < 0) + return 1; + + // Delete the object with the old values + int ret = api_data_delete(h, req, cbuf_get(delete_req_uri), pi, pretty, YANG_DATA_JSON, ds ); + cbuf_free(delete_req_uri); + if (ret != 0) + return ret; + + // Now set up for the post request. + // Strip /... from end of target val + // so that e.g. "/interface=eth2" becomes "/" + // or "/interface_list=mylist/interface=eth2" becomes "/interface_list=mylist/" + cbuf* post_req_uri = yang_patch_strip_after_last_slash(target_val); + + // Make post_req_uri something like "/restconf/data/ietf-interfaces:interfaces" + if (cbuf_append_str(simple_patch_request_uri, cbuf_get(post_req_uri))) + return 1; + cbuf_free(post_req_uri); + + // Now insert the new values into the data + // (which will include the key value and all other mandatory values) + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + // Convert the data to json + cbuf *json_simple_patch = cbuf_new(); + if (json_simple_patch == NULL) + return 1; + xml2json_cbuf(json_simple_patch, x_simple_patch, 1); + + // Send the POST request + ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); + + cbuf_free(json_simple_patch); + xml_free(value_vec_tmp); + return ret; +} + +/*! YANG PATCH create method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simplepatch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + */ +static int yang_patch_do_create ( + clicon_handle h, + void *req, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + cbuf* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj *x_simple_patch + ) +{ + cxobj * value_vec_tmp = NULL; + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); + return ret; +} + +/*! YANG PATCH insert method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pi Offset, where to start pcvec + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] api_path full API path, e.g. "/restconf/data/example-jukebox:jukebox/playlist=Foo-One" + * @param[in] point_val value in "point" field of edit in YANG patch + */ +static int yang_patch_do_insert ( + clicon_handle h, + void *req, + int pi, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + cbuf* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj *x_simple_patch, + cbuf* where_val, + char* api_path, + cbuf *point_val + ) +{ + cxobj * value_vec_tmp = NULL; + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + return 1; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, cbuf_get(where_val)); + cbuf *point_str = cbuf_new(); + if (point_str == NULL) + return 1; + cbuf_append_str(point_str, api_path); + cbuf_append_str(point_str, cbuf_get(point_val)); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + return 1; + } + cv_name_set(cv, "point"); + cv_string_set(cv, cbuf_get(point_str)); + + // Send the POST request + int ret = api_data_post(h, req, cbuf_get(simple_patch_request_uri), pi, qvec_tmp, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, ds ); + xml_free(value_vec_tmp); + cbuf_free(point_str); + cbuf_free(json_simple_patch); + return ret; +} + +/*! YANG PATCH merge method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource + * @param[in] simple_patch_request_uri URI for patch request, e.g. "/restconf/data/ietf-interfaces:interfaces" + * @param[in] value_vec_len number of elements in the "value" array of an edit in YANG patch + * @param[in] value_vec pointer to the "value" array of an edit in YANG patch + * @param[in] x_simple_patch pointer to XML containing module name, e.g. "" + * @param[in] where_val value in "where" field of edit in YANG patch + * @param[in] key_xn XML with key tag and value, e.g. "Foo-One" + */ +static int yang_patch_do_merge ( + clicon_handle h, + void *req, + cvec *pcvec, + int pi, + cvec *qvec, + int pretty, + restconf_media media_out, + ietf_ds_t ds, + cbuf* simple_patch_request_uri, + int value_vec_len, + cxobj** value_vec, + cxobj *x_simple_patch, + cxobj *key_xn + ) +{ + int ret = -1; + cxobj * value_vec_tmp = NULL; + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + cbuf *json_simple_patch = yang_patch_xml2json_modified_cbuf(x_simple_patch); + if (json_simple_patch == NULL) + return 1; + xml_free(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, cbuf_get(simple_patch_request_uri), pcvec, pi, qvec, cbuf_get(json_simple_patch), pretty, YANG_DATA_JSON, media_out, 1, ds ); + cbuf_free(cb); + cbuf_free(json_simple_patch); + } + return ret; +} + +/*! YANG PATCH method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] api_path0 According to restconf (Sec 3.5.3.1 in rfc8040) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * Netconf: (nc:operation="merge") + * See RFC8072 + * YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or + "remove" a resource within the target resource. + * Currently "move" not supported + */ +static int +api_data_yang_patch(clicon_handle h, + void *req, + char *api_path0, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out, + ietf_ds_t ds) +{ + int retval = -1; + int i; + cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ + cxobj *xbot_tmp = NULL; + yang_stmt *yspec; + char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + cvec *nsc = NULL; + yang_bind yb; + char *xpath = NULL; + cbuf *path_orig_1 = NULL; + + clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + api_path=api_path0; + /* strip /... from start */ + for (i=0; idisabled true false - truenonedefault
0.0.0.0
8081false
+ truenonedefault
0.0.0.0
80false
diff --git a/example/main/example_backend.c b/example/main/example_backend.c index cad6dc30..1d907030 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -1012,6 +1012,7 @@ example_exit(clicon_handle h) return 0; } +/* Forward declaration */ clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 30e114d0..920b45f4 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -108,7 +108,12 @@ * added to its parent but then it is more difficult to check trhe when condition. * This fix add the parent x0p as a "candidate" so that the xpath-eval function can use it as * an alernative if it exists. - * Note although this solves many usecases involving parents and absolute paths, itstill does not + * Note although this solves many usecases involving parents and absolute paths, it still does not * solve all usecases, such as absolute usecases where the added node is looked for */ #define XML_PARENT_CANDIDATE + +/*! Enable yang patch RFC 8072 + * Remove this when regression test + */ +#undef YANG_PATCH diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index f42cf33d..40b9bc96 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -127,8 +127,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message_xml(cxobj **xret, char *message); -int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); - +int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); int netconf_minmax_elements_xml(cxobj **xret, cxobj *xp, char *name, int max); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_module_features(clicon_handle h); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index fea7fd8a..e1427863 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -100,6 +100,7 @@ int xml_chardata_encode(char **escp, const char *fmt, ...); #endif int xml_chardata_cbuf_append(cbuf *cb, char *str); int uri_percent_decode(char *enc, char **str); + const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); int clicon_str2int_search(const map_str2int *mstab, char *str, int upper); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 8f4a288f..158d2cb8 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -76,5 +76,6 @@ int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); int xml_copy_marked(cxobj *x0, cxobj *x1); +int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 437318a7..879e6b8c 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -212,6 +212,10 @@ char *yang_when_xpath_get(yang_stmt *ys); int yang_when_xpath_set(yang_stmt *ys, char *xpath); cvec *yang_when_nsc_get(yang_stmt *ys); int yang_when_nsc_set(yang_stmt *ys, cvec *nsc); +const char *yang_filename_get(yang_stmt *ys); +int yang_filename_set(yang_stmt *ys, const char *filename); +int yang_linenum_get(yang_stmt *ys); +int yang_linenum_set(yang_stmt *ys, int linenum); /* Other functions */ yang_stmt *yspec_new(void); diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 0413d2c6..c4cf0293 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -557,7 +557,7 @@ xmldb_readfile(clicon_handle h, } cprintf(cberr, "Internal error: %s", clicon_err_reason); clicon_err_reset(); - if (netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) + if (xerr && netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) goto done; cbuf_free(cberr); goto fail; diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index b27733b4..caf79351 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -1238,7 +1238,7 @@ _json_parse(char *str, goto done; } cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x)); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) + if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) goto done; goto fail; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index febf5b93..8dadfbd5 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -131,6 +131,10 @@ netconf_invalid_value_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -248,6 +252,10 @@ netconf_missing_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -356,6 +364,10 @@ netconf_bad_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -449,6 +461,10 @@ netconf_common_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -705,6 +721,10 @@ netconf_access_denied_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -941,6 +961,10 @@ netconf_data_missing_xml(cxobj **xret, cxobj *xerr; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1005,6 +1029,10 @@ netconf_operation_not_supported_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1116,6 +1144,10 @@ netconf_operation_failed_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1200,6 +1232,10 @@ netconf_malformed_message_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1250,8 +1286,12 @@ netconf_data_not_unique_xml(cxobj **xret, cxobj *xerr; cxobj *xinfo; cbuf *cb = NULL; - cxobj *xa; + cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1316,6 +1356,10 @@ netconf_minmax_elements_xml(cxobj **xret, cbuf *cb = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1373,6 +1417,10 @@ netconf_trymerge(cxobj *x, char *reason = NULL; cxobj *xc; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_dup(x)) == NULL) goto done; diff --git a/lib/src/clixon_regex.c b/lib/src/clixon_regex.c index 60034ff4..73448bdc 100644 --- a/lib/src/clixon_regex.c +++ b/lib/src/clixon_regex.c @@ -89,6 +89,7 @@ * \p{Z} Separators [slp]? * \p{S} Symbols [mcko]? * \p{O} Other [cfon]? + * For non-printable, \n, \t, \r see https://www.regular-expressions.info/nonprint.html */ int regexp_xsd2posix(char *xsd, @@ -124,6 +125,9 @@ regexp_xsd2posix(char *xsd, case 'i': /* initial */ cprintf(cb, "[a-zA-Z_:]"); break; + case 'n': /* non-printable \n */ + cprintf(cb, "\n"); + break; case 'p': /* category escape: \p{IsCategory} */ j = i+1; if (j+2 < strlen(xsd) && @@ -161,12 +165,18 @@ regexp_xsd2posix(char *xsd, } /* if syntax error, just leave it */ break; + case 'r': /* non-printable */ + cprintf(cb, "\r"); + break; case 's': cprintf(cb, "[ \t\r\n]"); break; case 'S': cprintf(cb, "[^ \t\r\n]"); break; + case 't': /* non-printable */ + cprintf(cb, "\t"); + break; case 'w': /* word */ //cprintf(cb, "[0-9a-zA-Z_\\\\-]") cprintf(cb, "[^[:punct:][:space:][:cntrl:]]"); diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 274d339a..710d209b 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -114,11 +114,19 @@ validate_leafref(cxobj *xt, cvec *nsc = NULL; cbuf *cberr = NULL; char *path; + yang_stmt *ymod; if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if ((cberr = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cberr, "Leafref requires path statement"); + if (xret && netconf_missing_element_xml(xret, "application", + yang_argument_get(ytype), + cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -140,8 +148,13 @@ validate_leafref(cxobj *xt, clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); - if (netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) + ymod = ys_module(ys); + cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d", + leafrefbody, + path, + yang_argument_get(ymod), + yang_linenum_get(ys)); + if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -211,7 +224,7 @@ validate_identityref(cxobj *xt, /* Get idref value. Then see if this value is derived from ytype. */ if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ - if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) goto done; goto fail; } @@ -219,13 +232,13 @@ validate_identityref(cxobj *xt, goto done; /* This is the type's base reference */ if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) goto done; goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) goto done; goto fail; } @@ -239,8 +252,11 @@ validate_identityref(cxobj *xt, #endif } if (ymod == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; @@ -252,9 +268,12 @@ validate_identityref(cxobj *xt, */ idrefvec = yang_cvec_get(ybaseid); if (cvec_find(idrefvec, idref) == NULL){ - cprintf(cberr, "Identityref validation failed, %s not derived from %s", - node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d", + node, + yang_argument_get(ybaseid), + yang_argument_get(ys_module(ybaseid)), + yang_linenum_get(ybaseid)); + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -337,7 +356,7 @@ xml_yang_validate_rpc(clicon_handle h, goto done; /* Only accept resolved NETCONF base namespace */ if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){ - if (netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) + if (xret && netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) goto done; goto fail; } @@ -345,7 +364,7 @@ xml_yang_validate_rpc(clicon_handle h, /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL){ - if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) goto done; goto fail; } @@ -434,7 +453,7 @@ check_choice(cxobj *xt, continue; /* not choice */ break; } - if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) goto done; goto fail; } /* while */ @@ -487,7 +506,7 @@ check_list_key(cxobj *xt, while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + if (xret && netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) goto done; goto fail; } @@ -557,7 +576,7 @@ check_mandatory(cxobj *xt, goto done; } cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc))); - if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -574,7 +593,7 @@ check_mandatory(cxobj *xt, if (x == NULL){ /* @see RFC7950: 15.6 Error Message for Data That Violates * a Mandatory "choice" Statement */ - if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) + if (xret && netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) goto done; goto fail; } @@ -707,7 +726,7 @@ check_unique_list(cxobj *x, if (cvi==NULL){ /* Last element (i) is newly inserted, see if it is already there */ if (check_insert_duplicate(vec, i, vlen, sorted) < 0){ - if (netconf_data_not_unique_xml(xret, x, cvk) < 0) + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) goto done; goto fail; } @@ -751,7 +770,7 @@ check_min_max(cxobj *xp, if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ cv = yang_cv_get(ymin); if (nr < cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) goto done; goto fail; } @@ -760,7 +779,7 @@ check_min_max(cxobj *xp, cv = yang_cv_get(ymax); if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ nr > cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) goto done; goto fail; } @@ -851,7 +870,7 @@ check_list_unique_minmax(cxobj *xt, /* Only lists and leaf-lists are allowed to be many * This checks duplicate container and leafs */ - if (netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) goto done; goto fail; } @@ -1018,14 +1037,14 @@ xml_yang_validate_add(clicon_handle h, * are considered as "" */ cvtype = cv_type_get(cv); if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) goto done; goto fail; } } else{ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1033,7 +1052,7 @@ xml_yang_validate_add(clicon_handle h, if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0) goto done; if (ret == 0){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1136,6 +1155,7 @@ xml_yang_validate_all(clicon_handle h, char *ns = NULL; cbuf *cb = NULL; cvec *nsc = NULL; + int hit = 0; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ @@ -1158,7 +1178,7 @@ xml_yang_validate_all(clicon_handle h, goto done; if (ns) cprintf(cb, " in namespace: %s", ns); - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -1217,7 +1237,7 @@ xml_yang_validate_all(clicon_handle h, } cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s", xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", ye?yang_argument_get(ye):cbuf_get(cb)) < 0) goto done; goto fail; @@ -1227,53 +1247,21 @@ xml_yang_validate_all(clicon_handle h, nsc = NULL; } } - /* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ - if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ - xpath = yang_argument_get(yc); /* "when" has xpath argument */ - /* WHEN xpath needs namespace context */ - if (xml_nsctx_yang(ys, &nsc) < 0) + if (yang_check_when_xpath(xt, xml_parent(xt), ys, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; - if ((nr = xpath_vec_bool(xt, nsc, "%s", xpath)) < 0) + } + cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)", + xml_name(xt), + yang_argument_get(ys_module(ys)), + xpath); + if (xret && netconf_operation_failed_xml(xret, "application", + cbuf_get(cb)) < 0) goto done; - if (nsc){ - xml_nsctx_free(nsc); - nsc = NULL; - } - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed WHEN condition of %s in module %s", - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } - } - /* Second variants of WHEN: - * Augmented and uses when using special info in node - */ - if ((xpath = yang_when_xpath_get(ys)) != NULL){ - if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys), - "%s", xpath)) < 0) - goto done; - if (nr == 0){ - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'", - xpath, - xml_name(xt), - yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", - cbuf_get(cb)) < 0) - goto done; - goto fail; - } + goto fail; } } x = NULL; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 0b933992..0422e650 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1139,10 +1139,12 @@ xml_default1(yang_stmt *yt, int retval = -1; yang_stmt *yc; cxobj *xc; - int top=0; /* Top symbol (set default namespace) */ + int top = 0; /* Top symbol (set default namespace) */ int create = 0; char *xpath; - int nr; + int nr = 0; + int hit = 0; + cg_var *cv; if (xt == NULL){ /* No xml */ clicon_err(OE_XML, EINVAL, "No XML argument"); @@ -1162,14 +1164,17 @@ xml_default1(yang_stmt *yt, continue; switch (yang_keyword_get(yc)){ case Y_LEAF: - if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */ - /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + if ((cv = yang_cv_get(yc)) == NULL){ + clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should", + yang_argument_get(yc)); + goto done; + } + if (!cv_flag(cv, V_UNSET)){ /* Default value exists */ + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){ /* No such child exist, create this leaf */ if (xml_default_create(yc, xt, top) < 0) @@ -1180,13 +1185,11 @@ xml_default1(yang_stmt *yt, break; case Y_CONTAINER: if (yang_find(yc, Y_PRESENCE, NULL) == NULL){ - /* Check when statement from uses or augment */ - if ((xpath = yang_when_xpath_get(yc)) != NULL){ - if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0) - goto done; - if (nr == 0) - break; /* Do not create default if xpath fails */ - } + /* Check when condition */ + if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0) + goto done; + if (hit && nr == 0) + break; /* Do not create default if xpath fails */ /* If this is non-presence, (and it does not exist in xt) call * recursively and create nodes if any default value exist first. * Then continue and populate? @@ -2257,3 +2260,70 @@ xml_copy_marked(cxobj *x0, return retval; } +/*! Check when condition + * + * @param[in] h Clixon handle + * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp + * @param[in] xp XML parent + * @param[in] ys Yang node + * First variants of WHEN: Augmented and uses when using special info in node + * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. + */ +int +yang_check_when_xpath(cxobj *xn, + cxobj *xp, + yang_stmt *yn, + int *hit, + int *nrp, + char **xpathp) +{ + int retval = 1; + yang_stmt *yc; + char *xpath = NULL; + cxobj *x = NULL; + int nr = 0; + cvec *nsc = NULL; + int xmalloc = 0; /* ugly help variable to clean temporary object */ + int nscmalloc = 0; /* ugly help variable to remove */ + + /* First variant */ + if ((xpath = yang_when_xpath_get(yn)) != NULL){ + x = xp; + nsc = yang_when_nsc_get(yn); + *hit = 1; + } + /* Second variant */ + else if ((yc = yang_find(yn, Y_WHEN, NULL)) != NULL){ + xpath = yang_argument_get(yc); /* "when" has xpath argument */ + /* Create dummy */ + if (xn == NULL){ + if ((x = xml_new(yang_argument_get(yn), xp, CX_ELMNT)) == NULL) + goto done; + xml_spec_set(x, yn); + xmalloc++; + } + else + x = xn; + if (xml_nsctx_yang(yn, &nsc) < 0) + goto done; + nscmalloc++; + *hit = 1; + } + else + *hit = 0; + if (x && xpath){ + if ((nr = xpath_vec_bool(x, nsc, "%s", xpath)) < 0) + goto done; + } + if (nrp) + *nrp = nr; + if (xpathp) + *xpathp = xpath; + retval = 0; + done: + if (xmalloc) + xml_purge(x); + if (nsc && nscmalloc) + xml_nsctx_free(nsc); + return retval; +} diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 9bfd6d00..65a3cd49 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -65,6 +65,7 @@ #include #include #include +#include #include #include @@ -196,7 +197,7 @@ static int xml_parse_encoding(clixon_xml_yacc *xy, char *enc) { - if(strcmp(enc, "UTF-8")){ + if(strcasecmp(enc, "UTF-8")){ clicon_err(OE_XML, XMLPARSE_ERRNO, "Unsupported XML encoding: %s expected UTF-8", enc); free(enc); return -1; diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 64352055..43845787 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -290,11 +290,14 @@ xpath_tree2cbuf(xpath_tree *xs, switch (xs->xs_type){ case XP_AND: /* and or */ case XP_ADD: /* div mod + * - */ - case XP_RELEX: /* !=, >= <= < > = */ - case XP_UNION: if (xs->xs_c1) cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int)); break; + case XP_RELEX: /* !=, >= <= < > = */ + case XP_UNION: /* | */ + if (xs->xs_c1) + cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int)); + break; case XP_PATHEXPR: /* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7c1af5a1..7234fd09 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -340,11 +340,13 @@ yang_flag_reset(yang_stmt *ys, * @param[in] ys Yang statement * @retval xpath xpath should evaluate to true at validation * @retval NULL Not set + * Note xpath context is PARENT which is different from when actual when child which is + * child itself */ char* yang_when_xpath_get(yang_stmt *ys) { - return ys->ys_when_xpath; + return ys->ys_when_xpath; } /*! Set yang xpath and namespace context for "when"-associated augment @@ -414,6 +416,61 @@ yang_when_nsc_set(yang_stmt *ys, return retval; } +/*! Get yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval filename + * @note there maye not always be a "filename" in case the yang is read from memory + */ +const char * +yang_filename_get(yang_stmt *ys) +{ + return ys->ys_filename; +} + +/*! Set yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] filename + * @retval 0 OK + * @retval -1 Error + * @note there maye not always be a "filename" in case the yang is read from memory + */ +int +yang_filename_set(yang_stmt *ys, + const char *filename) +{ + if ((ys->ys_filename = strdup(filename)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + return -1; + } + return 0; +} + +/*! Get line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @retval linenum + */ +int +yang_linenum_get(yang_stmt *ys) +{ + return ys->ys_linenum; +} + +/*! Set line number of yang filename for error/debug purpose + * + * @param[in] ys Yang statement + * @param[in] linenum + */ +int +yang_linenum_set(yang_stmt *ys, + int linenum) +{ + ys->ys_linenum = linenum; + return 0; +} + /* End access functions */ /*! Create new yang specification @@ -496,6 +553,8 @@ ys_free1(yang_stmt *ys, cvec_free(ys->ys_when_nsc); if (ys->ys_stmt) free(ys->ys_stmt); + if (ys->ys_filename) + free(ys->ys_filename); if (self) free(ys); return 0; diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index d14e98ac..a1af0679 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -93,7 +93,8 @@ struct yang_stmt{ char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */ int _ys_vector_i; /* internal use: yn_each */ - + char *ys_filename; /* For debug/errors: filename (only (sub)modules) */ + int ys_linenum; /* For debug/errors: line number (in ys_filename) */ }; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 03e9695c..3718ceb8 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -112,6 +112,7 @@ modstate_diff_free(modstate_diff_t *md) * * Load RFC7895 yang spec, module-set-id, etc. * @param[in] h Clicon handle + * @see netconf_module_load */ int yang_modules_init(clicon_handle h) @@ -328,7 +329,7 @@ yang_modules_state_get(clicon_handle h, * Note, list is not sorted since it is state (should not be) */ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 6c44b2b9..3c144dcf 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -318,6 +318,7 @@ ysp_add(clixon_yang_yacc *yy, goto err; if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */ goto err2; /* dont free since part of tree */ + yang_linenum_set(ys, yy->yy_linenum); /* For error/debugging */ // done: return ys; err: @@ -662,7 +663,7 @@ yin_element_stmt1 : K_YIN_ELEMENT bool_str stmtend {free($2);} /* Identity */ identity_stmt : K_IDENTITY identifier_str ';' - { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); _PARSE_DEBUG("identity-stmt -> IDENTITY string ;"); } | K_IDENTITY identifier_str diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index c472f292..28c52d68 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -773,6 +773,9 @@ yang_parse_str(char *str, goto done; } ymod = yy.yy_module; + /* Add filename for debugging and errors, see also ys_linenum on (each symbol?) */ + if (yang_filename_set(ymod, name) < 0) + goto done; done: ystack_pop(&yy); if (yy.yy_stack) diff --git a/test/test_identity.sh b/test/test_identity.sh index d520205d..3e900669 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Identity and identityref tests # Example from RFC7950 Sec 7.18 and 9.10 +# Extended with a submodule # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -39,7 +40,7 @@ EOF # with two changes: the leaf statement is in the original module and # a transitive dependent identifier (foo) cat < $dir/example-crypto-base.yang - module example-crypto-base { +module example-crypto-base { yang-version 1.1; namespace "urn:example:crypto-base"; prefix "crypto"; @@ -59,12 +60,11 @@ cat < $dir/example-crypto-base.yang "Base identity used to identify public-key crypto algorithms."; } - } - +} EOF cat < $dir/example-des.yang - module example-des { +module example-des { yang-version 1.1; namespace "urn:example:des"; prefix "des"; @@ -81,14 +81,15 @@ cat < $dir/example-des.yang base "crypto:symmetric-key"; description "Triple DES crypto algorithm."; } - } +} EOF cat < $fyang - module example { +module example-my-crypto { yang-version 1.1; namespace "urn:example:my-crypto"; prefix mc; + include "example-sub"; import "example-crypto-base" { prefix "crypto"; } @@ -141,7 +142,46 @@ cat < $fyang base mc:empty; } } + uses myname; +} +EOF + +# Only included from sub-module +# Introduce an identity only visible by example-sub submodule +cat < $dir/example-extra.yang +module example-extra { + yang-version 1.1; + namespace "urn:example:extra"; + prefix ee; + identity extra-base; + identity extra-new{ + base ee:extra-base; + } + identity extra-old{ + base ee:extra-base; + } +} +EOF + +# Sub-module +cat < $dir/example-sub.yang +submodule example-sub { + yang-version 1.1; + belongs-to example-my-crypto { + prefix mc; } + import example-extra { + prefix ee; + } + grouping myname { + leaf sub-name { + description "Uses identity accessed by only the submodule"; + type identityref { + base ee:extra-base; + } + } + } +} EOF new "test params: -f $cfg" @@ -212,7 +252,7 @@ new "Set crypto to foo:bar" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOfoo:bar]]>]]>" "^]]>]]>$" new "netconf validate (expect fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg in example-crypto-base.yang:[0-9]*]]>]]>$" new "cli set crypto to mc:aes" expectpart "$($clixon_cli -1 -f $cfg -l o set crypto mc:aes)" 0 "^$" @@ -242,7 +282,7 @@ new "Netconf set undefined acl-type" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOxundefined]]>]]>" "^]]>]]>$" new "netconf validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base]]>]]>" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, undefined not derived from acl-base in example-my-crypto.yang:[0-9]*]]>]]>" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -269,42 +309,61 @@ expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" +# Special case sub-module +new "auto-cli cli expansion submodule identity" +expectpart "$(echo "set sub-name ?" | $clixon_cli -f $cfg 2>&1)" 0 "set sub-name" "ee:extra-new" "ee:extra-old" + +new "cli add identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:extra-new)" 0 "" + +new "cli validate submodule identity" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "" + +new "cli add wrong identity" +expectpart "$($clixon_cli -1 -f $cfg -l o set sub-name ee:foo)" 0 "" + +new "cli validate wrong id (expect fail)" +expectpart "$($clixon_cli -1 -f $cfg -l o validate 2>&1)" 255 "Identityref validation failed, ee:foo not derived from extra-base" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" + # restconf and identities: # 1. set identity in own module with restconf (PUT and POST), read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf # 3. set identity in other module with netconf, read it with restconf and netconf new "restconf add own identity" -expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example:aes"}')" 0 "HTTP/$HVER 201" +expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-my-crypto:aes"}')" 0 "HTTP/$HVER 201" new "restconf get own identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"aes"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"aes"}' new "netconf get own identity as set by restconf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^aes" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 2. set identity in other module with restconf , read it with restconf and netconf if ! $YANG_UNKNOWN_ANYDATA ; then new "restconf add POST instead of PUT (should fail)" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-my-crypto:crypto -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}' fi # Alternative error: #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' new "restconf add other (des) identity using POST" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example:crypto" +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data -d '{"example-my-crypto:crypto":"example-des:des3"}')" 0 "HTTP/$HVER 201" "Location: $RCPROTO://localhost/restconf/data/example-my-crypto:crypto" new "restconf get other identity" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" new "restconf delete identity" -expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 204" +expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 204" # 3. set identity in other module with netconf, read it with restconf and netconf new "netconf set other identity" @@ -314,7 +373,7 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "restconf get other identity (set by netconf)" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:crypto)" 0 "HTTP/$HVER 200" '{"example:crypto":"example-des:des3"}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-my-crypto:crypto)" 0 "HTTP/$HVER 200" '{"example-my-crypto:crypto":"example-des:des3"}' new "netconf get other identity" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^des:des3" diff --git a/test/test_leaf_default.sh b/test/test_leaf_default.sh index 4dbd1032..d36553c0 100755 --- a/test/test_leaf_default.sh +++ b/test/test_leaf_default.sh @@ -67,6 +67,19 @@ module example{ default 31; /* should be set on startup */ } } + /* Extra rules to check when condition */ + leaf npleaf{ + when "../s3 = '99'"; + type uint32; + default 98; + } + container npcont{ + when "../s3 = '99'"; + leaf npext{ + type uint32; + default 99; + } + } } container p4{ presence "A presence container"; @@ -144,6 +157,13 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^$XMLafalsefalse]]>]]>$" +# Set s3 leaf to 99 triggering when condition for default values +new "Set s3 to 99" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO99]]>]]>" "^]]>]]>$" + +new "get config np3 with npleaf and npext" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^99319899]]>]]>$" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill diff --git a/test/test_leafref.sh b/test/test_leafref.sh index a7a414c3..a86e411d 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -140,7 +140,7 @@ new "leafref add non-existing ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" new "leafref validate" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth3errorLeafref validation failed: No leaf eth3 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" #new "leafref wrong ref" #expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth3
10.0.4.6
]]>]]>" "^]]>]]>$" @@ -170,7 +170,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOeth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementeth0errorLeafref validation failed: No leaf eth0 matching path /if:interfaces/if:interface/if:name in example.yang:[0-9]*]]>]]>$" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index 4c4c046a..4c5032a2 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -209,7 +209,7 @@ new "leafref augment+leafref config wrong ref" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$" new "leafref augment+leafref validate wrong ref" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationbad-elementxxxerrorLeafref validation failed: No leaf xxx matching path /ex:sender/ex:name in augment.yang:[0-9]*]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 9ed123ad..31ce8cd5 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -168,10 +168,10 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / state-only should fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name. Internal error, state callback returned invalid XML]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$" new "netconf get / config-only ok" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^y]]>]]>$" diff --git a/test/test_netconf_filter.sh b/test/test_netconf_filter.sh index 73b5e4f6..ec7a9fcf 100755 --- a/test/test_netconf_filter.sh +++ b/test/test_netconf_filter.sh @@ -68,6 +68,12 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^operation-failedapplicatioerrorfilter type not supportedtype]]>]]>$" +new "get-config subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + +new "get subtree one (subtree implicit)" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" + new "get-config subtree one" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO1]]>]]>" "^11]]>]]>$" diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh index 7460efbe..a0b23ac5 100755 --- a/test/test_netconf_hello.sh +++ b/test/test_netconf_hello.sh @@ -58,6 +58,10 @@ wait_backend new "Netconf snd hello with xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' +# Hello, lowercase encoding +new "Netconf snd hello with xmldecl (lowercase encoding)" +expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' + new "Netconf snd hello without xmldecl" expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$' '^$' diff --git a/test/test_pattern.sh b/test/test_pattern.sh index 35c6d416..695c8926 100755 --- a/test/test_pattern.sh +++ b/test/test_pattern.sh @@ -368,6 +368,15 @@ module pattern{ '[0-9]|25[0-5])$'; } } + leaf p47 { + description "draft-wwlh-netconf-list-pagination-00 module example-social"; + type string { + length "1..80"; + pattern '.*[\n].*' { + modifier invert-match; + } + } + } } } EOF @@ -741,6 +750,11 @@ testrun "p$pnr" true '255.149.90.121' testrun "p$pnr" true '251.148.80.69' testrun "p$pnr" false '248:197.7.89/8' +let pnr=47 # '.*[\n].* +testrun "p$pnr" true 'Ensure all nights are cold' +testrun "p$pnr" false 'kalle foo' +testrun "p$pnr" false '01234567890123456789012345678901234567890123456789012345678901234567890123456789zzz' + # CLI tests new "CLI tests for RFC7950 Sec 9.4.7 ex 2 AB" expectpart "$($clixon_cli -1f $cfg -l o set c rfc2 AB)" 0 '^$' diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e449f5..4eb894f0 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,8 +23,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -54,7 +52,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index 70a94c37..f57c7cf9 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,8 +34,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -68,7 +66,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 12c94da3..83bc9a6a 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -117,7 +117,7 @@ new "when get config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example (WHEN xpath is ../type='static')]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" diff --git a/test/test_xml.sh b/test/test_xml.sh index 2d6f57d6..65d76646 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -127,6 +127,10 @@ expecteof "$clixon_util_xml -ol o" 255 '' '' 2> /dev/nul new "XMLdecl version + encoding" expecteof "$clixon_util_xml -o" 0 '' '' +# XML processors SHOULD match character encoding names in a case-insensitive way +new "XMLdecl encoding case-insensitive" +expecteof "$clixon_util_xml -o" 0 '' '' + new "XMLdecl version + wrong encoding" expecteof "$clixon_util_xml -o" 255 '' '' 2> /dev/null diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index e541234b..d339d31c 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -48,13 +48,13 @@ new "xpath canonical form (other)" expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:a -n j:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate 1" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y = 'e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y='e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate self" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form descendants" -expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255 diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index f996ad1a..3b2cfbee 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -124,7 +124,7 @@ new "Set site to fie which invalidates the when contains" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOfie]]>]]>" "^]]>]]>" new "netconf validate not OK" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example (WHEN xpath is contains(../../class,'foo') or contains(../../class,'bar'))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -140,13 +140,13 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" new "netconf validate not OK (mtu not allowed on self)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" @@ -162,7 +162,7 @@ new "Change type to atm" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0atm]]>]]>" "^]]>]]>" new "netconf validate not OK (crc not allowed)" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example']]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(type, \"ex:ethernet\"))]]>]]>$" new "Change type to ethernet (self)" expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLOe0ethernet]]>]]>" "^]]>]]>" diff --git a/test/vagrant/vagrant.sh b/test/vagrant/vagrant.sh index efadf216..4c80d5ef 100755 --- a/test/vagrant/vagrant.sh +++ b/test/vagrant/vagrant.sh @@ -169,7 +169,7 @@ case $release in native) $sshcmd sudo yum install -y libevent openssl $sshcmd sudo yum install -y libevent-devel openssl-devel - $sshcmd sudo dnf config-manager --set-enabled powertools + $sshcmd sudo yum-config-manager --enable powertools $sshcmd sudo yum install -y libnghttp2-devel ;; esac @@ -214,7 +214,6 @@ case $release in $sshcmd sudo apt install -y nginx ;; native) -# $sshcmd sudo apt install -y libevent-2.1 $sshcmd sudo apt install -y libssl-dev $sshcmd sudo apt install -y libevent-dev # evhtp $sshcmd sudo apt install -y libnghttp2-dev # nghttp2 diff --git a/util/Makefile.in b/util/Makefile.in index f857033e..5737529b 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c APPSRC += clixon_util_regexp.c APPSRC += clixon_util_socket.c -# APPSRC += clixon_util_validate.c +APPSRC += clixon_util_validate.c APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 555662be..ddd83935 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -41,7 +41,7 @@ datarootdir = @datarootdir@ # See also OPT_YANG_INSTALLDIR for the standard yang files YANG_INSTALLDIR = @YANG_INSTALLDIR@ -YANGSPECS = clixon-config@2021-05-20.yang # 5.2 +YANGSPECS = clixon-config@2021-07-11.yang # 5.3 YANGSPECS += clixon-lib@2021-03-08.yang # 5.1 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang diff --git a/yang/clixon/clixon-config.yang b/yang/clixon/clixon-config.yang deleted file mode 120000 index 91d563e9..00000000 --- a/yang/clixon/clixon-config.yang +++ /dev/null @@ -1 +0,0 @@ -clixon-config@2021-03-08.yang \ No newline at end of file diff --git a/yang/clixon/clixon-config@2021-03-08.yang b/yang/clixon/clixon-config@2021-07-11.yang similarity index 89% rename from yang/clixon/clixon-config@2021-03-08.yang rename to yang/clixon/clixon-config@2021-07-11.yang index eb217e2e..c4dea419 100644 --- a/yang/clixon/clixon-config@2021-03-08.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -43,12 +43,33 @@ module clixon-config { ***** END LICENSE BLOCK *****"; + revision 2021-07-11 { + description + "Added option: + CLICON_SYSTEM_CAPABILITIES + Removed default value: + CLICON_RESTCONF_INSTALLDIR + Marked as obsolete: + CLICON_YANG_LIST_CHECK + (Will be) Released in Clixon 5.3"; + } + revision 2021-05-20 { + description + "Added option: + CLICON_RESTCONF_USER + CLICON_RESTCONF_PRIVILEGES + CLICON_RESTCONF_INSTALLDIR + CLICON_RESTCONF_STARTUP_DONTUPDATE + CLICON_NETCONF_MESSAGE_ID_OPTIONAL + Released in Clixon 5.2"; + } revision 2021-03-08 { description "Added option: CLICON_NETCONF_HELLO_OPTIONAL CLICON_CLI_AUTOCLI_EXCLUDE - CLICON_XMLDB_UPGRADE_CHECKOLD"; + CLICON_XMLDB_UPGRADE_CHECKOLD + Released in Clixon 5.1"; } revision 2020-12-30 { description @@ -171,6 +192,10 @@ module clixon-config { "Commit startup configuration into running state After reboot when no persistent running db exists"; } + enum running-startup{ + description + "First try running db, if it is empty try startup db."; + } } } typedef datastore_format{ @@ -406,7 +431,11 @@ module clixon-config { "If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2: The 'key' statement, which MUST be present if the list represents configuration. Some yang specs seem not to fulfil this. However, if you reset this, there may - be follow-up errors due to code that assumes a configuration list has keys"; + be follow-up errors due to code that assumes a configuration list has keys + Marked as obsolete since the observation above seemed to be related to the + yang-data extension in RFC8040 allows non-key lists. This has been implemented + by a YANG_FLAG_NOKEY yang flag mechanism"; + status obsolete; } leaf CLICON_YANG_UNKNOWN_ANYDATA{ type boolean; @@ -421,6 +450,18 @@ module clixon-config { only loading from startup but may occur in other circumstances as well. This means that sanity checks of erroneous XML/JSON may not be properly signalled."; } + leaf CLICON_SYSTEM_CAPABILITIES { + type boolean; + default false; + description + "Enable module ietf-system-capabilities and ietf-notification-capabilities + Note: There are several dependencies: + - ietf-yang-library revision 2019-01-04 is REQUIRED + - nacm + - ietf-yang-structure-ext.yang, + - ietf-yang-instance-data + see draft-ietf-netconf-notification-capabilities-17"; + } leaf CLICON_BACKEND_DIR { type string; description @@ -451,6 +492,16 @@ module clixon-config { is returned, which conforms to the RFC. Note this applies only to external NETCONF, not the internal (IPC) netconf"; } + leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL { + type boolean; + default false; + description + "This option relates to RFC 6241 Sec 4.1 Element + The element has a mandatory attribute 'message-id', which is a + string chosen by the sender of the RPC. + If true, an RPC can be sent without a message-id. + This applies to both external NETCONF and internal (IPC) netconf"; + } leaf CLICON_RESTCONF_DIR { type string; description @@ -470,7 +521,33 @@ module clixon-config { Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead"; status obsolete; } - + leaf CLICON_RESTCONF_INSTALLDIR { + type string; + description + "If set, path to dir of clixon-restconf daemon binary as used by backend if + started internally (run-time). + If this path is not set, clixon_restconf will be looked for according to + configured installdir: $(sbindir) (install-time) + Since programs can be moved around at install/cross-compile time the installed + dir may be difficult to know at install time, which is the reason why + CLICON_RESTCONF_INSTALLDIR exists, in order to override the Makefile + installdir. + Note on the installdir, DESTDIR is not included since according to man pages: + by specifying DESTDIR should not change the operation of the software in + any way, so its value should not be included in any file contents. "; + } + leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { + type boolean; + default false; + description + "According to RFC 8040 Sec 1.4: + If the NETCONF server supports :startup, the RESTCONF server MUST automatically + update the [...] startup configuration [...] as a consequence of a RESTCONF + edit operation. + Setting this option disables this behaviour, ie the startup configuration is NOT + automatically updated. + If this option is false, the startup is autoamtically updated following the RFC"; + } leaf CLICON_RESTCONF_PRETTY { type boolean; default true; @@ -486,6 +563,26 @@ module clixon-config { Note: Obsolete, use pretty in clixon-restconf.yang instead"; status obsolete; } + leaf CLICON_RESTCONF_USER { + type string; + description + "Run clixon_daemon as this user + When drop privileges is used, the daemon will drop privileges to this user. + In pre-5.2 code this was configured as compile-time constant WWWUSER with + default value www-data + See also CLICON_PRIVILEGES setting"; + default www-data; + } + leaf CLICON_RESTCONF_PRIVILEGES { + type priv_mode; + default drop_perm; + description + "Restconf privileges mode. + If drop_perm or drop_temp then drop privileges to CLICON_RESTCONF_USER. + If the platform does not support getresuid and accompanying functions, the mode + must be set to 'none'. + "; + } leaf CLICON_CLI_DIR { type string; description @@ -557,6 +654,7 @@ module clixon-config { clixon-restconf means generate autocli for all models except clixon-restconf.yang The value can be a list of space separated module names"; + default "clixon-restconf"; } leaf CLICON_CLI_VARONLY { type int32; @@ -706,7 +804,7 @@ module clixon-config { user (eg datastores). It also sets the backend unix socket owner to this user, but its group is set by CLICON_SOCK_GROUP. - See also CLICON_PRIVILEGES setting"; + See also CLICON_BACKEND_PRIVILEGES setting"; } leaf CLICON_BACKEND_PRIVILEGES { type priv_mode; diff --git a/yang/clixon/clixon-lib@2020-12-30.yang b/yang/clixon/clixon-lib@2020-12-30.yang deleted file mode 100644 index c3780d1e..00000000 --- a/yang/clixon/clixon-lib@2020-12-30.yang +++ /dev/null @@ -1,180 +0,0 @@ -module clixon-lib { - yang-version 1.1; - namespace "http://clicon.org/lib"; - prefix cl; - - organization - "Clicon / Clixon"; - - contact - "Olof Hagsand "; - - description - "Clixon Netconf extensions for communication between clients and backend. - - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand - Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2020-12-30 { - description - "Changed: RPC process-control output parameter status to pid"; - } - revision 2020-12-08 { - description - "Added: autocli-op extension. - rpc process-control for process/daemon management - Released in clixon 4.9"; - } - revision 2020-04-23 { - description - "Added: stats RPC for clixon XML and memory statistics. - Added: restart-plugin RPC for restarting individual plugins without restarting backend."; - } - revision 2019-08-13 { - description - "No changes (reverted change)"; - } - revision 2019-06-05 { - description - "ping rpc added for liveness"; - } - revision 2019-01-02 { - description - "Released in Clixon 3.9"; - } - typedef service-operation { - type enumeration { - enum start { - description - "Start if not already running"; - } - enum stop { - description - "Stop if running"; - } - enum restart { - description - "Stop if running, then start"; - } - enum status { - description - "Check status"; - } - } - description - "Common operations that can be performed on a service"; - } - extension autocli-op { - description - "Takes an argument an operation defing how to modify the clispec at - this point in the YANG tree for the automated generated CLI. - Note that this extension is only used in clixon_cli. - Operations is expected to be extended, but the following operations are defined: - - hide This command is active but not shown by ? or TAB"; - argument cliop; - } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } - rpc ping { - description "Check aliveness of backend daemon."; - } - rpc stats { - description "Clixon XML statistics."; - output { - container global{ - description "Clixon global statistics"; - leaf xmlnr{ - description "Number of XML objects: number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - } - list datastore{ - description "Datastore statistics"; - key "name"; - leaf name{ - description "name of datastore (eg running)."; - type string; - } - leaf nr{ - description "Number of XML objects. That is number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - leaf size{ - description "Size in bytes of internal datastore cache of datastore tree."; - type uint64; - } - } - - } - } - rpc restart-plugin { - description "Restart specific backend plugins."; - input { - leaf-list plugin { - description "Name of plugin to restart"; - type string; - } - } - } - - rpc process-control { - description - "Control a specific process or daemon: start/stop, etc. - This is for direct managing of a process by the backend. - Alternatively one can manage a daemon via systemd, containerd, kubernetes, etc."; - input { - leaf name { - description "Name of process"; - type string; - mandatory true; - } - leaf operation { - type service-operation; - mandatory true; - description - "One of the strings 'start', 'stop', 'restart', or 'status'."; - } - } - output { - leaf pid { - description "Process-id of running process or 0 if not running - Value is only valid for operation status"; - type uint32; - } - } - } -} diff --git a/yang/clixon/clixon-restconf@2021-03-15.yang b/yang/clixon/clixon-restconf@2021-03-15.yang deleted file mode 100644 index 7180054a..00000000 --- a/yang/clixon/clixon-restconf@2021-03-15.yang +++ /dev/null @@ -1,221 +0,0 @@ -module clixon-restconf { - yang-version 1.1; - namespace "http://clicon.org/restconf"; - prefix "clrc"; - - import ietf-inet-types { - prefix inet; - } - - organization - "Clixon"; - - contact - "Olof Hagsand "; - - description - "This YANG module provides a data-model for the Clixon RESTCONF daemon. - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON - - Licensed under the Apache License, Version 2.0 (the \"License\"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an \"AS IS\" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the \"GPL\"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK *****"; - - revision 2021-03-15 { - description - "make authentication-type none a feature - Added flag to enable core dumps"; - } - revision 2020-12-30 { - description - "Added: debug field - Added 'none' as default value for auth-type - Changed http-auth-type enum from 'password' to 'user'"; - } - revision 2020-10-30 { - description - "Initial release"; - } - - feature fcgi { - description - "This feature indicates that the restconf server supports the fast-cgi reverse - proxy solution. - That is, a reverse proxy is the HTTP front-end and the restconf daemon listens - to a fcgi socket. - The alternative is the internal HTTP solution using evhtp."; - } - - feature allow-auth-none { - description - "This feature allows the use of authentication-type none."; - } - - typedef http-auth-type { - type enumeration { - enum none { - if-feature "allow-auth-none"; - description - "Incoming message are set to authenticated by default. No ca-auth callback is called, - Authenticated user is set to special user 'none'. - Typically assumes NACM is not enabled."; - } - enum client-certificate { - description - "TLS client certificate validation is made on each incoming message. If it passes - the authenticated user is extracted from the SSL_CN parameter - The ca-auth callback can be used to revise this behavior."; - } - enum user { - description - "User-defined authentication as defined by the ca-auth callback. - One example is some form of password authentication, such as basic auth."; - } - } - description - "Enumeration of HTTP authorization types."; - } - grouping clixon-restconf{ - description - "HTTP RESTCONF configuration."; - leaf enable { - type boolean; - default "false"; - description - "Enables RESTCONF functionality. - Note that starting/stopping of a restconf daemon is different from it being - enabled or not. - For example, if the restconf daemon is under systemd management, the restconf - daemon will only start if enable=true."; - } - leaf auth-type { - type http-auth-type; - description - "The authentication type. - Note client-certificate applies only if ssl-enable is true and socket has ssl"; - default user; - } - leaf debug { - description - "Set debug level of restconf daemon. - 0 is no debug, 1 is debugging, more is detailed debug. - Debug logs will be directed to syslog with - ident: clixon_restconf and PID - facility: LOG_USER - level: LOG_DEBUG"; - type uint32; - default 0; - } - leaf enable-core-dump { - description - "enable core dumps. - this is a no-op on systems that don't support it."; - type boolean; - default false; - } - leaf pretty { - type boolean; - default true; - description - "Restconf return value pretty print. - Restconf clients may add HTTP header: - Accept: application/yang-data+json, or - Accept: application/yang-data+xml - to get return value in XML or JSON. - RFC 8040 examples print XML and JSON in pretty-printed form. - Setting this value to false makes restconf return not pretty-printed - which may be desirable for performance or tests - This replaces the CLICON_RESTCONF_PRETTY option in clixon-config.yang"; - } - /* From this point only specific options - * First fcgi-specific options - */ - leaf fcgi-socket { - if-feature fcgi; /* Set by default by fcgi clixon_restconf daemon */ - type string; - default "/www-data/fastcgi_restconf.sock"; - description - "Path to FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock - Only if with-restconf=fcgi, NOT evhtp - This replaces CLICON_RESTCONF_PATH option in clixon-config.yang"; - } - /* Second, evhtp-specific options */ - leaf server-cert-path { - type string; - description - "Path to server certificate file. - Note only applies if socket has ssl enabled"; - } - leaf server-key-path { - type string; - description - "Path to server key file - Note only applies if socket has ssl enabled"; - } - leaf server-ca-cert-path { - type string; - description - "Path to server CA cert file - Note only applies if socket has ssl enabled"; - } - list socket { - description - "List of server sockets that the restconf daemon listens to"; - key "namespace address port"; - leaf namespace { - type string; - description - "Network namespace. - On platforms where namespaces are not suppported, 'default' - Default value can be changed by RESTCONF_NETNS_DEFAULT"; - } - leaf address { - type inet:ip-address; - description "IP address to bind to"; - } - leaf port { - type inet:port-number; - description "TCP port to bind to"; - } - leaf ssl { - type boolean; - default true; - description "Enable for HTTPS otherwise HTTP protocol"; - } - } - } - container restconf { - description - "This presence is strictly not necessary since the enable flag - in clixon-restconf is the flag bearing the actual semantics. - However, removing the presence leads to default config in all - clixon installations, even those which do not use backend-started restconf. - One could see this as mostly cosmetically annoying. - Alternative would be to make the inclusion of this yang conditional."; - presence "Enables RESTCONF"; - uses clixon-restconf; - } -} diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index ca68b32a..f2fb8c9d 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -50,6 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-yang-library@2019-01-04.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-datastores@2018-02-14.yang +YANGSPECS += ietf-yang-patch@2017-02-22.yang all: diff --git a/yang/mandatory/ietf-yang-patch@2017-02-22.yang b/yang/mandatory/ietf-yang-patch@2017-02-22.yang new file mode 100644 index 00000000..d0029ed2 --- /dev/null +++ b/yang/mandatory/ietf-yang-patch@2017-02-22.yang @@ -0,0 +1,390 @@ +module ietf-yang-patch { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch"; + prefix "ypatch"; + + import ietf-restconf { prefix rc; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for the YANG Patch and YANG Patch Status data structures. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The YANG grouping statements provide a normative syntax + for XML and JSON message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8072; see + the RFC itself for full legal notices."; + + revision 2017-02-22 { + description + "Initial revision."; + reference + "RFC 8072: YANG Patch Media Type."; + } + + typedef target-resource-offset { + type string; + description + "Contains a data resource identifier string representing + a sub-resource within the target resource. + The document root for this expression is the + target resource that is specified in the + protocol operation (e.g., the URI for the PATCH request). + + This string is encoded according to the same rules as those + for a data resource identifier in a RESTCONF request URI."; + reference + "RFC 8040, Section 3.5.3."; + } + + rc:yang-data "yang-patch" { + uses yang-patch; + } + + rc:yang-data "yang-patch-status" { + uses yang-patch-status; + } + + grouping yang-patch { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch edit request message."; + + container yang-patch { + description + "Represents a conceptual sequence of datastore edits, + called a patch. Each patch is given a client-assigned + patch identifier. Each edit MUST be applied + in ascending order, and all edits MUST be applied. + If any errors occur, then the target datastore MUST NOT + be changed by the YANG Patch operation. + + It is possible for a datastore constraint violation to occur + due to any node in the datastore, including nodes not + included in the 'edit' list. Any validation errors MUST + be reported in the reply message."; + + reference + "RFC 7950, Section 8.3."; + + leaf patch-id { + type string; + mandatory true; + description + "An arbitrary string provided by the client to identify + the entire patch. Error messages returned by the server + that pertain to this patch will be identified by this + 'patch-id' value. A client SHOULD attempt to generate + unique 'patch-id' values to distinguish between + transactions from multiple clients in any audit logs + maintained by the server."; + } + + leaf comment { + type string; + description + "An arbitrary string provided by the client to describe + the entire patch. This value SHOULD be present in any + audit logging records generated by the server for the + patch."; + } + + list edit { + key edit-id; + ordered-by user; + + description + "Represents one edit within the YANG Patch request message. + The 'edit' list is applied in the following manner: + + - The first edit is conceptually applied to a copy + of the existing target datastore, e.g., the + running configuration datastore. + - Each ascending edit is conceptually applied to + the result of the previous edit(s). + - After all edits have been successfully processed, + the result is validated according to YANG constraints. + - If successful, the server will attempt to apply + the result to the target datastore."; + + leaf edit-id { + type string; + description + "Arbitrary string index for the edit. + Error messages returned by the server that pertain + to a specific edit will be identified by this value."; + } + + leaf operation { + type enumeration { + enum create { + description + "The target data node is created using the supplied + value, only if it does not already exist. The + 'target' leaf identifies the data node to be + created, not the parent data node."; + } + enum delete { + description + "Delete the target node, only if the data resource + currently exists; otherwise, return an error."; + } + + enum insert { + description + "Insert the supplied value into a user-ordered + list or leaf-list entry. The target node must + represent a new data resource. If the 'where' + parameter is set to 'before' or 'after', then + the 'point' parameter identifies the insertion + point for the target node."; + } + enum merge { + description + "The supplied value is merged with the target data + node."; + } + enum move { + description + "Move the target node. Reorder a user-ordered + list or leaf-list. The target node must represent + an existing data resource. If the 'where' parameter + is set to 'before' or 'after', then the 'point' + parameter identifies the insertion point to move + the target node."; + } + enum replace { + description + "The supplied value is used to replace the target + data node."; + } + enum remove { + description + "Delete the target node if it currently exists."; + } + } + mandatory true; + description + "The datastore operation requested for the associated + 'edit' entry."; + } + + leaf target { + type target-resource-offset; + mandatory true; + description + "Identifies the target data node for the edit + operation. If the target has the value '/', then + the target data node is the target resource. + The target node MUST identify a data resource, + not the datastore resource."; + } + + leaf point { + when "(../operation = 'insert' or ../operation = 'move')" + + "and (../where = 'before' or ../where = 'after')" { + description + "This leaf only applies for 'insert' or 'move' + operations, before or after an existing entry."; + } + type target-resource-offset; + description + "The absolute URL path for the data node that is being + used as the insertion point or move point for the + target of this 'edit' entry."; + } + + leaf where { + when "../operation = 'insert' or ../operation = 'move'" { + description + "This leaf only applies for 'insert' or 'move' + operations."; + } + type enumeration { + enum before { + description + "Insert or move a data node before the data resource + identified by the 'point' parameter."; + } + enum after { + description + "Insert or move a data node after the data resource + identified by the 'point' parameter."; + } + + enum first { + description + "Insert or move a data node so it becomes ordered + as the first entry."; + } + enum last { + description + "Insert or move a data node so it becomes ordered + as the last entry."; + } + } + default last; + description + "Identifies where a data resource will be inserted + or moved. YANG only allows these operations for + list and leaf-list data nodes that are + 'ordered-by user'."; + } + + anydata value { + when "../operation = 'create' " + + "or ../operation = 'merge' " + + "or ../operation = 'replace' " + + "or ../operation = 'insert'" { + description + "The anydata 'value' is only used for 'create', + 'merge', 'replace', and 'insert' operations."; + } + description + "Value used for this edit operation. The anydata 'value' + contains the target resource associated with the + 'target' leaf. + + For example, suppose the target node is a YANG container + named foo: + + container foo { + leaf a { type string; } + leaf b { type int32; } + } + + The 'value' node contains one instance of foo: + + + + some value + 42 + + + "; + } + } + } + + } // grouping yang-patch + + grouping yang-patch-status { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch Status response + message."; + + container yang-patch-status { + description + "A container representing the response message sent by the + server after a YANG Patch edit request message has been + processed."; + + leaf patch-id { + type string; + mandatory true; + description + "The 'patch-id' value used in the request."; + } + + choice global-status { + description + "Report global errors or complete success. + If there is no case selected, then errors + are reported in the 'edit-status' container."; + + case global-errors { + uses rc:errors; + description + "This container will be present if global errors that + are unrelated to a specific edit occurred."; + } + leaf ok { + type empty; + description + "This leaf will be present if the request succeeded + and there are no errors reported in the 'edit-status' + container."; + } + } + + container edit-status { + description + "This container will be present if there are + edit-specific status responses to report. + If all edits succeeded and the 'global-status' + returned is 'ok', then a server MAY omit this + container."; + + list edit { + key edit-id; + + description + "Represents a list of status responses, + corresponding to edits in the YANG Patch + request message. If an 'edit' entry was + skipped or not reached by the server, + then this list will not contain a corresponding + entry for that edit."; + + leaf edit-id { + type string; + description + "Response status is for the 'edit' list entry + with this 'edit-id' value."; + } + + choice edit-status-choice { + description + "A choice between different types of status + responses for each 'edit' entry."; + leaf ok { + type empty; + description + "This 'edit' entry was invoked without any + errors detected by the server associated + with this edit."; + } + case errors { + uses rc:errors; + description + "The server detected errors associated with the + edit identified by the same 'edit-id' value."; + } + } + } + } + } + } // grouping yang-patch-status + +}