diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4188a4..ad2ebd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,61 @@ # Clixon Changelog -## 3.9.0 (Preliminary Target: 31 December 2018) +## 3.9.0 (Preliminary Target: Mid-January 2019) ### Planned new features * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features +* Correct XML namespace handling + * XML multiple modules was based on non-strict semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208) as well as strict Netconf and Restconf namespace handling, which causes problems with overlapping names and false positives, and most importantly, with standard conformance. + * There are still the following non-strict namespace handling: + * Everything in ietf-netconf base syntax with namespace `urn:ietf:params:xml:ns:netconf:base:1.0` is default and need not be explicitly given + * edit-config xpath select statement does not support namespaces + * notifications do not support namespaces. + * Below see netconf old (but wrong) netconf RPC: + ``` + + + + + + ipv4 + + + ``` + This is the currently correct Netconf RPC: + ``` + # xmlns may be ommitted + + + + + ipv4 + + + ``` + * Another example for restconf rpc with new correct syntax. Note that while Netconf uses xmlns attribute syntax, Restconf uses module name prefix. First the request: + ``` + POST http://localhost/restconf/operations/example:example) + Content-Type: application/yang-data+json + { + "example:input":{ + "x":0 + } + } + ``` + then the reply: + ``` + HTTP/1.1 200 OK + { + "example:output": { + "x": "0", + "y": "42" + } + } + ``` + * To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false. + * See https://github.com/clicon/clixon/issues/49 * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. @@ -27,25 +77,15 @@ * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. -* Correct XML namespace handling - * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: - ``` - # Wrong but accepted - # Correct - - - ``` - * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) - * XML to JSON translator support for mapping xmlns attribute to module name prefix. - * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" - * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) -* Removed delete-config support for candidate db since it is not supported in RFC6241. +* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. + * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. +* Removed `delete-config` support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. * XML namespace handling is corrected (see above) - * For backward compatibility set config option CLICON_XML_NS_ITERATE + * For backward compatibility set config option CLICON_XML_NS_LOOSE * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. * Change all @datamodel:tree to @datamodel in all CLI specification files @@ -53,6 +93,9 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. + * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` +* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Changed all make tags --> make TAGS @@ -273,7 +316,7 @@ translate { ### Known issues * Namespace name relabeling is not supported. - * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as: + * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as: ``` x:des3 ``` diff --git a/DEVELOP.md b/DEVELOP.md index fb66e641..74d7f4ef 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -97,6 +97,9 @@ EOF ## New release What to think about when doing a new release. +* valgrind for memory leaks +* New clixon-config.yang revision? +Tagging: * git merge --no-ff develop * change CLIXON_VERSION in configure.ac * git tag -a +``` +In 3.9, the same statement should be, for example: +``` + +``` +Note that base netconf syntax is still not enforced but recommended: +``` + + + +``` + Yang ==== YANG and XML is the heart of Clixon. Yang modules are used as a @@ -153,6 +170,9 @@ Clixon does not support the following netconf features: - edit-config config-text - edit-config operation +Some other deviations from the RFC: +- edit-config xpath select statement does not support namespaces + Restconf ======== Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index baf40269..3e1fa1ad 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -292,7 +292,7 @@ client_get_streams(clicon_handle h, * @param[in,out] xret Existing XML tree, merge x into this * @retval -1 Error (fatal) * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (clicon_err called) */ static int client_statedata(clicon_handle h, @@ -309,15 +309,15 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - (retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - (retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") && - (retval = yang_modules_state_get(h, yspec, xret)) != 0) - goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")) + if ((retval = yang_modules_state_get(h, yspec, xret)) != 0) + goto done; if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0) goto done; /* Code complex to filter out anything that is outside of xpath */ @@ -339,7 +339,7 @@ client_statedata(clicon_handle h, /* reset flag */ if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; - retval = 0; + retval = 0; /* OK */ done: if (xvec) free(xvec); @@ -363,7 +363,6 @@ from_client_get(clicon_handle h, char *xpath = "/"; cxobj *xret = NULL; int ret; - cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL) @@ -379,33 +378,24 @@ from_client_get(clicon_handle h, clicon_err_reset(); if ((ret = client_statedata(h, xpath, &xret)) < 0) goto done; - if (ret == 0){ /* OK */ - cprintf(cbret, ""); - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, "data") < 0) - goto done; - if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) - goto done; - } - cprintf(cbret, ""); - } - else { /* 1 Error from callback */ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); + if (ret == 1){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) goto done; - } - cprintf(cbx, "Internal error:%s", clicon_err_reason); - if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0) - goto done; - clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe)); + goto ok; } + cprintf(cbret, ""); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, "data") < 0) + goto done; + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); ok: retval = 0; done: - if (cbx) - cbuf_free(cbx); if (xret) xml_free(xret); return retval; @@ -433,6 +423,7 @@ from_client_edit_config(clicon_handle h, int non_config = 0; yang_spec *yspec; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec9"); @@ -491,24 +482,27 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ - if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) + if (clicon_xml_sort(h) && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xmldb_put(h, target, operation, xc, cbret) < 0){ + if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) goto done; goto ok; } + if (ret == 0) + goto ok; } + assert(cbuf_len(cbret) == 0); + cprintf(cbret, ""); ok: - if (!cbuf_len(cbret)) - cprintf(cbret, ""); retval = 0; done: if (cbx) cbuf_free(cbx); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; + } /* from_client_edit_config */ /*! Internal message: Lock database diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 88ea9e99..7c29131a 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -165,12 +165,24 @@ validate_common(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } - /* 2. Parse xml trees */ + /* 2. Parse xml trees + * This is the state we are going from */ if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0) goto done; + /* This is the state we are going to */ if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) goto done; + /* Validate the target state. It is not completely clear this should be done + * here. It is being made in generic_validate below. + * But xml_diff requires some basic validation, at least check that yang-specs + * have been assigned + */ + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* 3. Compute differences */ if (xml_diff(yspec, td->td_src, @@ -240,7 +252,7 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval -1 Error - or validation failed (but cbret not set) + * @retval -1 Error - or validation failed * @retval 0 Validation failed (with cbret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail @@ -259,7 +271,9 @@ candidate_commit(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate). Note this is only call that uses 3-values */ + /* Common steps (with validate). Load candidate and running and compute diffs + * Note this is only call that uses 3-values + */ if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; if (ret == 0) @@ -270,12 +284,14 @@ candidate_commit(clicon_handle h, goto done; /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL)) < 0) goto done; + if (ret == 0) + goto fail; + } /* 8. Success: Copy candidate to running */ - if (xmldb_copy(h, candidate, "running") < 0) goto done; @@ -423,9 +439,11 @@ from_client_validate(clicon_handle h, goto ok; } /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) - goto done; + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0) + goto done; + goto ok; + } cprintf(cbret, ""); ok: retval = 0; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 8e433d85..ba297852 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -176,22 +176,24 @@ db_reset(clicon_handle h, } /*! Merge db1 into db2 without commit + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int db_merge(clicon_handle h, const char *db1, - const char *db2) + const char *db2, + cbuf *cbret) { - int retval = -1; - cxobj *xt = NULL; - + int retval = -1; + cxobj *xt = NULL; + /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; /* Merge xml into db2. Without commit */ - if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret); done: if (xt) xml_free(xt); @@ -288,18 +290,22 @@ nacm_load_external(clicon_handle h) } /*! Merge xml in filename into database + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int load_extraxml(clicon_handle h, char *filename, - const char *db) + const char *db, + cbuf *cbret) { int retval = -1; cxobj *xt = NULL; int fd = -1; - + if (filename == NULL) - return 0; + return 1; if ((fd = open(filename, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; @@ -310,9 +316,7 @@ load_extraxml(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret); done: if (fd != -1) close(fd); @@ -385,9 +389,13 @@ static int startup_mode_running(clicon_handle h, char *extraxml_file) { - int retval = -1; - cbuf *cbret = NULL; + int retval = -1; + cbuf *cbret = NULL; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; @@ -400,41 +408,46 @@ startup_mode_running(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } /* Commit original running. Assume -1 is validate fail */ - if (candidate_commit(h, "candidate", cbret) < 1){ - /* (1) We cannot differentiate between fatal errors and validation - * failures - * (2) If fatal error, we should exit - * (3) If validation fails we cannot continue. How could we? - * (4) Need to restore the running db since we destroyed it above - */ - clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - /* Reinstate original */ - if (xmldb_copy(h, "candidate", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "candidate", cbret) < 1) + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* (1) We cannot differentiate between fatal errors and validation + * failures + * (2) If fatal error, we should exit + * (3) If validation fails we cannot continue. How could we? + * (4) Need to restore the running db since we destroyed it above + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + /* Reinstate original */ + if (xmldb_copy(h, "candidate", "running") < 0) + goto done; + goto done; } /*! Clixon startup startup mode: Commit startup configuration into running state @@ -460,11 +473,16 @@ startup -------------------------+--| */ static int startup_mode_startup(clicon_handle h, - char *extraxml_file) + char *extraxml_file) { int retval = -1; cbuf *cbret = NULL; + /* Create return buffer for netconf xml errors */ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) goto done; @@ -481,34 +499,25 @@ startup_mode_startup(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - /* Create return buffer (not used) */ - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + /* Commit startup */ - if (candidate_commit(h, "startup", cbret) < 1){ /* diff */ - /* We cannot differentiate between fatal errors and validation - * failures - * In both cases we copy back the original running and quit - */ - clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - if (xmldb_copy(h, "backup", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "startup", cbret) < 1) /* diff */ + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "backup") < 0) @@ -516,6 +525,20 @@ startup_mode_startup(clicon_handle h, if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* We cannot differentiate between fatal errors and validation + * failures + * In both cases we copy back the original running and quit + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + if (xmldb_copy(h, "backup", "running") < 0) + goto done; + goto done; } int @@ -540,7 +563,6 @@ main(int argc, int sockfamily; char *xmldb_plugin; int xml_cache; - int xml_pretty; char *xml_format; char *nacm_mode; int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; @@ -808,9 +830,10 @@ main(int argc, if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0) if (xmldb_setopt(h, "format", (void*)xml_format) < 0) goto done; - if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) - if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0) - goto done; + if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0) + goto done; + if (xmldb_setopt(h, "sort", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XML_SORT")) < 0) + goto done; /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index e8ce2972..996a27cb 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -117,7 +117,7 @@ clixon_plugin_reset(clicon_handle h, * @param[in,out] xtop State XML tree is merged with existing tree. * @retval -1 Error * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (xret set with netconf-error) * @note xtop can be replaced */ int diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 7e6f1307..f6796f58 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,8 +236,8 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) + goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -293,7 +293,7 @@ cli_set(clicon_handle h, cvec *cvv, cvec *argv) { - int retval = 1; + int retval = -1; if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0) goto done; @@ -501,53 +501,54 @@ cli_start_shell(clicon_handle h, cvec *vars, cvec *argv) { - char *cmd; + char *cmd; struct passwd *pw; - int retval; - char bcmd[128]; - cg_var *cv1 = cvec_i(vars, 1); + int retval = -1; + char bcmd[128]; + cg_var *cv1 = cvec_i(vars, 1); cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL); if ((pw = getpwuid(getuid())) == NULL){ fprintf(stderr, "%s: getpwuid: %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } if (chdir(pw->pw_dir) < 0){ fprintf(stderr, "%s: chdir(%s): %s\n", __FUNCTION__, pw->pw_dir, strerror(errno)); endpwent(); - return -1; + goto done; } endpwent(); cli_signal_flush(h); cli_signal_unblock(h); if (cmd){ snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd); - if ((retval = system(bcmd)) < 0){ + if (system(bcmd) < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash -c): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } } else - if ((retval = system("bash -l")) < 0){ + if (system("bash -l") < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } cli_signal_block(h); #if 0 /* Allow errcodes from bash */ if (retval != 0){ fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval); - return -1; + goto done; } #endif - - return 0; + retval = 0; + done: + return retval; } /*! Generic quit callback diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2e56f329..ec39ac4e 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -171,7 +171,7 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) goto done; if (y==NULL) goto ok; @@ -443,6 +443,7 @@ cli_show_config(clicon_handle h, cxobj *xc; cxobj *xerr; enum genmodel_type gt; + yang_spec *yspec; if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,]", cvec_len(argv)); @@ -496,6 +497,13 @@ cli_show_config(clicon_handle h, clicon_rpc_generate_error("Get configuration", xerr); goto done; } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + /* Some formats (eg cli) require yang */ + if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* Print configuration according to format */ switch (format){ case FORMAT_XML: @@ -516,7 +524,7 @@ cli_show_config(clicon_handle h, if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) goto done; xc = NULL; /* Dont print xt itself */ - while ((xc = xml_child_each(xt, xc, -1)) != NULL) + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) xml2cli(stdout, xc, NULL, gt); /* cli syntax */ break; case FORMAT_NETCONF: diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index dc57126e..4cdef1a0 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -118,6 +118,8 @@ process_incoming_packet(clicon_handle h, free(str0); if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ isrpc++; + if (xml_spec_populate_rpc(h, xrpc, yspec) < 0) + goto done; if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 4d9b354a..7945110e 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -905,13 +905,9 @@ netconf_application_rpc(clicon_handle h, goto ok; } yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); - if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL) - break; - } + if ((yrpc==NULL) && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(xn, yspec, &yrpc) < 0) /* Y_RPC */ + goto done; } /* Check if found */ if (yrpc != NULL){ diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 972ab99f..2295e3fa 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -1,11 +1,11 @@ # Clixon Restconf - * [Installation](#Installation) - * [Streams](Streams) - * [Nchan Streams](Nchan) - * [Debugging](Debugging) + * [Installation](#installation) + * [Streams](#streams) + * [Nchan Streams](#nchan-streams) + * [Debugging](#debugging) -### 1. Installation +## 1. Installation The examples are based on Nginx. Other reverse proxies should work but are not verified. @@ -44,39 +44,39 @@ sudo systemctl start start.service Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data +> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data ``` Make restconf calls with curl ``` -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces +> curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces [ { - "interfaces": { + "ietf-interfaces:interfaces": { "interface":[ { - "name": "eth0", - "type": "eth", - "enabled": "true", "name": "eth9", - "type": "eth", - "enabled": "true" + "type": "ex:eth", + "enabled": true, } ] } } ] -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type -[ - { - "type": "eth" - } -] - -curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data +``` +Get the type of a specific interface: +``` +> curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type +{ + "ietf-interfaces:type": "eth" +} +``` +Example of writing a new interfaces specification: +``` +curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}' ``` -### 2. Streams +## 2. Streams Clixon have two experimental restconf event stream implementations following RFC8040 Section 6 using SSE. One native and one using Nginx @@ -112,7 +112,7 @@ Add the following to extend the nginx configuration file with the following stat AN example of a stream access is as follows: ``` -vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE +> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE data: 2018-11-04T14:47:11.373124faultEthernet0major data: 2018-11-04T14:47:16.375265faultEthernet0major @@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing See (stream tests)[../test/test_streams.sh] for more examples. -### 3. Nchan +## 3. Nchan As an alternative streams implementation, Nginx/Nchan can be used. Nginx uses pub/sub channels and can be configured in a variety of @@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. -### 4. Debugging +## 4. Debugging Start the restconf fastcgi program with debug flag: ``` diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index a11d55c1..b2910a1b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -93,6 +93,7 @@ Mapping netconf error-tag -> status code | malformed-message | 400 | +-------------------------+-------------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H @@ -195,20 +196,26 @@ api_data_get2(clicon_handle h, size_t xlen; int i; cxobj *x; - + int ret; + clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbpath = cbuf_new()) == NULL) goto done; cprintf(cbpath, "/"); - clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); /* We know "data" is element pi-1 */ - if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ + if ((ret = api_path2xpath(yspec, pcvec, pi, cbpath)) < 0) + goto done; + if (ret == 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); @@ -216,24 +223,29 @@ api_data_get2(clicon_handle h, if (clicon_rpc_get(h, path, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } + if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* We get return via netconf which is complete tree from root * We need to cut that tree to only the object. */ -#if 1 /* DEBUG */ - { +#if 0 /* DEBUG */ + if (debug){ cbuf *cb = cbuf_new(); clicon_xml2cbuf(cb, xret, 0, 0); clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); cbuf_free(cb); } #endif - /* Check if error return XXX this needs more work */ - if ((xe = xpath_first(xret, "/rpc-error")) != NULL){ + /* Check if error return */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -241,11 +253,12 @@ api_data_get2(clicon_handle h, /* Normal return, no error */ if ((cbx = cbuf_new()) == NULL) goto done; - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); - FCGX_FPrintF(r->out, "\r\n"); - if (head) + if (head){ + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); goto ok; + } if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */ if (use_xml){ if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */ @@ -261,16 +274,32 @@ api_data_get2(clicon_handle h, goto done; if (use_xml){ for (i=0; i0 + * Out: {"example:x": {"0"}} + */ if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; + } } - // clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); ok: @@ -414,7 +443,8 @@ api_data_post(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; /* dont free */ char *username; - + int ret; + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); @@ -429,25 +459,45 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the @@ -456,9 +506,12 @@ api_data_post(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -471,6 +524,19 @@ api_data_post(clicon_handle h, /* Replace xbot with x, ie bottom of api-path with data */ if (xml_addsub(xbot, x) < 0) goto done; + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -509,7 +575,7 @@ api_data_post(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) /* Use original xe */ goto done; goto ok; } @@ -644,6 +710,8 @@ api_data_put(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; char *username; + int ret; + char *namespace0; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); @@ -659,46 +727,91 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + else{ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; - goto ok; - } + goto ok; + } + } /* The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Add operation (create/replace) as attribute */ if ((xa = xml_new("operation", x, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif /* Replace xparent with x, ie bottom of api-path with data */ if (api_path==NULL && strcmp(xml_name(x),"data")==0){ if (xml_addsub(NULL, x) < 0) @@ -713,9 +826,12 @@ api_data_put(clicon_handle h, if (strcmp(xml_name(x), xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ @@ -723,9 +839,12 @@ api_data_put(clicon_handle h, if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -733,8 +852,15 @@ api_data_put(clicon_handle h, xml_purge(xbot); if (xml_addsub(xparent, x) < 0) goto done; + /* If we already have that default namespace, remove it in child */ + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL){ + if (xml2ns(xparent, NULL, &namespace0) < 0) + goto done; + /* Set xmlns="" default namespace attribute (if diff from default) */ + if (strcmp(namespace0, xml_value(xa))==0) + xml_purge(xa); + } } - /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -859,6 +985,8 @@ api_data_delete(clicon_handle h, cxobj *xretdis = NULL; /* return from discard */ cxobj *xerr = NULL; char *username; + int ret; + cxobj *xe; /* xml error, no free */ clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -871,8 +999,22 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -891,8 +1033,8 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -905,7 +1047,7 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); @@ -914,7 +1056,7 @@ api_data_delete(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -981,36 +1123,41 @@ api_operations_get(clicon_handle h, char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; + int i; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "{\"operations\": "); ymod = NULL; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); - yc = NULL; + yc = NULL; i=0; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + if (use_xml) + cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + else{ + if (i==0) + cprintf(cbx, "{"); + if (i) + cprintf(cbx, ","); + cprintf(cbx, "\"%s:%s\": null", ymod->ys_argument, yc->ys_argument); + } + i++; } + if (!use_xml && i) + cprintf(cbx, "}"); } - cprintf(cbx, ""); - if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) - goto done; - if (xml_rootchild(xt, 0, &xt) < 0) - goto done; - cbuf_reset(cbx); /* reuse same cbuf */ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ - goto done; - } - else{ - if (xml2json_cbuf(cbx, xt, pretty) < 0) - goto done; - } + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "}"); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); @@ -1027,6 +1174,298 @@ api_operations_get(clicon_handle h, return retval; } +/*! Handle input data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] data Stream input data + * @param[in] yspec Yang top-level specification + * @param[in] yrpc Yang rpc spec + * @param[in] xrpc XML pointer to rpc method + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * + * RFC8040 3.6.1 + * If the "rpc" or "action" statement has an "input" section, then + * instances of these input parameters are encoded in the module + * namespace where the "rpc" or "action" statement is defined, in an XML + * element or JSON object named "input", which is in the module + * namespace where the "rpc" or "action" statement is defined. + * (Any other input is assumed as error.) + */ +static int +api_operations_post_input(clicon_handle h, + FCGX_Request *r, + char *data, + yang_spec *yspec, + yang_stmt *yrpc, + cxobj *xrpc, + int pretty, + int use_xml, + int parse_xml) +{ + int retval = -1; + cxobj *xdata = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; + cxobj *xinput; + cxobj *x; + cbuf *cbret = NULL; + + clicon_debug(1, "%s %s", __FUNCTION__, data); + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (xml_parse_string(data, yspec, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + else { /* JSON */ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* Special case for JSON: It looks like: + * Need to translate to + */ + if (json2xml_ns(yspec, xdata, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + xml_name_set(xdata, "data"); + /* Here xdata is: + * ... + * Validate that exactly only tag + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xinput),"input") != 0 || + xml_child_nr_type(xdata, CX_ELMNT) != 1){ + + if (xml_child_nr_type(xdata, CX_ELMNT) == 0){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have input statement") < 0) + goto done; + } + else + if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + // clicon_debug(1, "%s input validation passed", __FUNCTION__); + /* Add all input under path */ + x = NULL; + while ((x = xml_child_i_type(xinput, 0, CX_ELMNT)) != NULL) + if (xml_addsub(xrpc, x) < 0) + goto done; + /* Here xrpc is: 42 + */ + // ok: + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + if (xdata) + xml_free(xdata); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Handle output data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] xret XML reply messages from backend/handler + * @param[in] yspec Yang top-level specification + * @param[in] youtput Yang rpc output specification + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[out] xoutputp Restconf JSON/XML output + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * xret should like: 0 + */ +static int +api_operations_post_output(clicon_handle h, + FCGX_Request *r, + cxobj *xret, + yang_spec *yspec, + yang_stmt *youtput, + char *namespace, + int pretty, + int use_xml, + cxobj **xoutputp) + +{ + int retval = -1; + cxobj *xoutput = NULL; + cxobj *xerr = NULL; /* assumed malloced, will be freed */ + cxobj *xe; /* just pointer */ + cxobj *xa; /* xml attribute (xmlns) */ + cxobj *x; + cxobj *xok; + cbuf *cbret = NULL; + int ret; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Validate that exactly only tag */ + if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xoutput),"rpc-reply") != 0 || + xml_child_nr_type(xret, CX_ELMNT) != 1){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* xoutput should now look: 0 */ + /* 9. Translate to restconf RPC data */ + xml_name_set(xoutput, "output"); + /* xoutput should now look: 0 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0) + goto done; + clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* Validate output (in case handlers are wrong) */ + if (youtput==NULL){ + /* Special case, no yang output + * RFC 7950 7.14.4 + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element + * RFC 8040 3.6.2 + * If the "rpc" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + if ((xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xok),"ok") != 0 || + xml_child_nr_type(xoutput, CX_ELMNT) != 1){ + /* Internal error - invalid output from rpc handler */ + if (xok){ + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply is not ok") < 0) + goto done; + } + else + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply should have OK") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + goto fail; + } + else{ + xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) + goto done; + if (ret == 1 && + (ret = xml_yang_validate_add(xoutput, cbret)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + + /* Clear namespace of parameters */ + x = NULL; + while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) { + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_purge(xa) < 0) + goto done; + } + } + /* Set namespace on output */ + if (xmlns_set(xoutput, NULL, namespace) < 0) + goto done; + *xoutputp = xoutput; + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + return retval; + fail: + retval = 0; + goto done; +} + /*! REST operation POST method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle @@ -1040,7 +1479,24 @@ api_operations_get(clicon_handle h, * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * See RFC 8040 Sec 3.6 / 4.4.2 * @note We map post to edit-config create. - POST {+restconf}/operations/ + * POST {+restconf}/operations/ + * 1. Initialize + * 2. Get rpc module and name from uri (oppath) and find yang spec + * 3. Build xml tree with user and rpc: + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + * 5. Translate input args to Netconf RPC, add to xml tree: + * 42 + * 6. Validate outgoing RPC and fill in default values + * 4299 + * 7. Send to RPC handler, either local or backend + * 8. Receive reply from local/backend handler as Netconf RPC + * 0 + * 9. Translate to restconf RPC data: + * JSON: {"example:output":{"x":0}} + * XML: 0 + * 10. Validate and send reply to originator */ int api_operations_post(clicon_handle h, @@ -1057,20 +1513,15 @@ api_operations_post(clicon_handle h, int retval = -1; int i; char *oppath = path; - yang_stmt *yrpc = NULL; yang_spec *yspec; - yang_stmt *yinput; - yang_stmt *youtput; - cxobj *xdata = NULL; + yang_stmt *youtput = NULL; + yang_stmt *yrpc = NULL; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced must be freed */ - cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; yang_node *y = NULL; - cxobj *xinput; - cxobj *xoutput; - cxobj *x; + cxobj *xoutput = NULL; cxobj *xa; cxobj *xe; char *username; @@ -1079,8 +1530,10 @@ api_operations_post(clicon_handle h, char *prefix = NULL; char *id = NULL; yang_stmt *ys = NULL; + char *namespace = NULL; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); + /* 1. Initialize */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -1094,216 +1547,188 @@ api_operations_post(clicon_handle h, if (oppath == NULL || strcmp(oppath,"/")==0){ if (netconf_operation_failed_xml(&xerr, "protocol", "Operation name expected") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); - - /* Find yang rpc statement, return yang rpc statement if found + /* 2. Get rpc module and name from uri (oppath) and find yang spec * POST {+restconf}/operations/ * * The field identifies the module name and rpc identifier * string for the desired operation. */ - if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ + if (nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ goto done; if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ - clicon_debug(1, "%s rpc %s not found", __FUNCTION__, id); if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - /* Create an xml message: - * <"rpc">... - * eg + /* 3. Build xml tree with user and rpc: + * */ - /* Create config top-of-tree */ if ((xtop = xml_new("rpc", NULL, NULL)) == NULL) goto done; xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ + /* Here xtop is: */ if ((username = clicon_username_get(h)) != NULL){ if ((xa = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, username) < 0) goto done; + /* Here xtop is: */ } - /* XXX: something strange for rpc user */ - if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) + if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y)) < 0) goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is: + * xbot is + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + */ + namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR); + clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data); if (data && strlen(data)){ - /* Parse input data as json or xml into xml */ - if (parse_xml){ - if (xml_parse_string(data, NULL, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot, + pretty, use_xml, parse_xml)) < 0) + goto done; + if (ret == 0) goto ok; - } - yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL); - /* xdata should have format */ - if ((xinput = xpath_first(xdata, "/input")) == NULL){ - xml_name_set(xdata, "input"); - xml_spec_set(xdata, yinput); /* needed for xml_spec_populate */ - if (yinput){ - if ((ret = xml_yang_validate_add(xdata, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - } - else{ - /* Add all input under path */ - x = NULL; - while (xml_child_nr(xinput)){ - x = xml_child_i(xinput, 0); - if (xml_addsub(xbot, x) < 0) - goto done; - } - if (yinput){ - xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ - /* XXX yinput <-> h ?*/ - if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xbot, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s err: %s", __FUNCTION__, cbuf_get(cbret)); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xbot, cbret)) < 0){ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if (xml_apply0(xbot, CX_ELMNT, xml_default, NULL) < 0) - goto done; - } - } } - ret = 0; - xe = NULL; - while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { - /* Look for local (client-side) restconf plugins. */ - if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + /* Here xtop is: + 42 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) goto done; - if (ret == 1){ /* Handled locally */ - if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) - goto done; - /* Local error: return it and quit */ - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - break; /* Just one if local */ + clicon_debug(1, "%s 5. Translate input args: %s", + __FUNCTION__, cbuf_get(ccc)); } - if (ret == 0){ /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) +#endif + /* 6. Validate outgoing RPC and fill in defaults */ + if (xml_spec_populate_rpc(h, xtop, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_rpc(xtop, cbret)) < 0) + goto done; + if (ret == 0){ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is (default values): + * 4299 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 6. Validate and defaults:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* 7. Send to RPC handler, either local or backend + * Note (1) xtop is xbot is + * (2) local handler wants and backend wants + */ + /* Look for local (client-side) restconf plugins. + * -1:Error, 0:OK local, 1:OK backend + */ + if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) + goto done; + /* Local error: return it and quit */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } } - /* Check if RPC output section */ - if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) == NULL){ - /* If the RPC operation is invoked without errors and if the "rpc" or - * "action" statement has no "output" section, the response message - * MUST NOT include a message-body and MUST send a "204 No Content" - * status-line instead. - */ - FCGX_SetExitStatus(204, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - goto ok; + else { /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } } - if ((cbx = cbuf_new()) == NULL) + /* 8. Receive reply from local/backend handler as Netconf RPC + * 0 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 8. Receive reply:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL); + if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace, + pretty, use_xml, &xoutput)) < 0) goto done; - if ((xoutput=xpath_first(xret, "/")) != NULL){ - xml_name_set(xoutput, "output"); - cbuf_reset(cbx); - xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s output validation failed", __FUNCTION__); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - /* Sanity check of outgoing XML */ + if (ret == 0) + goto ok; + /* xoutput should now look: 0 */ FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); - if (xoutput){ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xoutput, 0, pretty) < 0) - goto done; - } - else - if (xml2json_cbuf(cbx, xoutput, pretty) < 0) - goto done; - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + cbuf_reset(cbret); + if (use_xml){ + if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0) + goto done; + /* xoutput should now look: 0 */ } + else{ + if (xml2json_cbuf(cbret, xoutput, pretty) < 0) + goto done; + /* xoutput should now look: {"example:output": {"x":0,"y":42}} */ + } + FCGX_FPrintF(r->out, "%s", cbuf_get(cbret)); + FCGX_FPrintF(r->out, "\r\n\r\n"); ok: retval = 0; done: @@ -1312,16 +1737,12 @@ api_operations_post(clicon_handle h, free(prefix); if (id) free(id); - if (xdata) - xml_free(xdata); if (xtop) xml_free(xtop); if (xret) xml_free(xret); if (xerr) xml_free(xerr); - if (cbx) - cbuf_free(cbx); if (cbret) cbuf_free(cbret); return retval; diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 08424521..ec20f373 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -249,7 +249,7 @@ restconf_stream(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cb, "%s", name); + cprintf(cb, "%s", name); /* Print all fields */ for (i=0; ith_format; else if (strcmp(optname, "pretty") == 0) *value = &th->th_pretty; + else if (strcmp(optname, "sort") == 0) + *value = &th->th_sort; else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -288,6 +292,9 @@ text_setopt(xmldb_handle xh, else if (strcmp(optname, "pretty") == 0){ th->th_pretty = (intptr_t)value; } + else if (strcmp(optname, "sort") == 0){ + th->th_sort = (intptr_t)value; + } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -358,6 +365,16 @@ xml_copy_marked(cxobj *x0, assert(x0 && x1); yt = xml_spec(x0); /* can be null */ + /* Copy all attributes */ + x = NULL; + while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) { + name = xml_name(x); + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + } + /* Go through children to detect any marked nodes: * (3) Special case: key nodes in lists are copied if any * node in list is marked @@ -470,6 +487,10 @@ text_get(xmldb_handle xh, if (singleconfigroot(xt, &xt) < 0) goto done; } + /* XXX: should we validate file if read from disk? + * Argument against: we may want to have a semantically wrong file and wish + * to edit? + */ } /* xt == NULL */ /* Here xt looks like: ... */ @@ -530,12 +551,15 @@ text_get(xmldb_handle xh, /* Add default values (if not set) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; - /* Order XML children according to YANG */ - if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) - goto done; -#if 0 /* debug */ - if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) - clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__); + /* Order XML children according to YANG. + * XXX: should this be !th->th_sort? + */ + if (!th->th_sort) + if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) + goto done; +#if 1 /* debug */ + if (th->th_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) + clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); #endif if (debug>1) clicon_xml2file(stderr, xt, 0, 1); @@ -555,6 +579,7 @@ text_get(xmldb_handle xh, } /*! Modify a base tree x0 with x1 with yang spec y according to operation op + * @param[in] th text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 @@ -562,10 +587,11 @@ text_get(xmldb_handle xh, * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * Assume x0 and x1 are same on entry and that y is the spec - * @see put in clixon_keyvalue.c + * @see text_modify_top */ static int -text_modify(cxobj *x0, +text_modify(struct text_handle *th, + cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1, @@ -576,6 +602,8 @@ text_modify(cxobj *x0, char *opstr; char *x1name; char *x1cname; /* child name */ + cxobj *x0a; /* attribute */ + cxobj *x1a; /* attribute */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ @@ -607,6 +635,13 @@ text_modify(cxobj *x0, // int iamkey=0; if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } #if 0 /* If it is key I dont want to mark it */ if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0) @@ -679,6 +714,13 @@ text_modify(cxobj *x0, if (x0==NULL){ if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } @@ -699,7 +741,7 @@ text_modify(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; x0vec[i++] = x0c; } @@ -709,7 +751,7 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -740,6 +782,7 @@ text_modify(cxobj *x0, } /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 + * @param[in] th text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) @@ -812,23 +855,19 @@ text_modify_top(struct text_handle *th, goto done; if (ymod != NULL) yc = yang_find_datanode((yang_node*)ymod, x1cname); - if (yc == NULL && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL) - break; - } + if (yc == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0) + goto done; } if (yc == NULL){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) goto done; goto ok; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -840,7 +879,7 @@ text_modify_top(struct text_handle *th, return retval; } /* text_modify_top */ -/*! For containers without presence and no children, remove +/*! For containers without presence and no children(except attrs), remove * @param[in] x XML tree node * See section 7.5.1 in rfc6020bis-02.txt: * No presence: @@ -869,7 +908,7 @@ xml_container_presence(cxobj *x, } /* Mark node that is: container, have no children, dont have presence */ if (y->ys_keyword == Y_CONTAINER && - xml_child_nr(x)==0 && + xml_child_nr_notype(x, CX_ATTR)==0 && yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL) xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */ retval = 0; @@ -964,7 +1003,7 @@ text_put(xmldb_handle xh, goto done; #endif #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); #endif /* @@ -975,7 +1014,7 @@ text_put(xmldb_handle xh, goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) - goto ok; + goto fail; /* Remove NONE nodes if all subs recursively are also NONE */ if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) @@ -990,7 +1029,7 @@ text_put(xmldb_handle xh, if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) goto done; #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); #endif /* Write back to datastore cache if first time */ @@ -1025,8 +1064,7 @@ text_put(xmldb_handle xh, } else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) goto done; - ok: - retval = 0; + retval = 1; done: if (cbretlocal && cbret) cbuf_free(cbret); @@ -1041,6 +1079,9 @@ text_put(xmldb_handle xh, if (!th->th_cache && x0) xml_free(x0); return retval; + fail: + retval = 0; + goto done; } /*! Copy database from db1 to db2 @@ -1434,7 +1475,7 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) + if (xmldb_put(h, db, op, NULL, xn, NULL) < 1) goto done; } else diff --git a/doc/FAQ.md b/doc/FAQ.md index 9ecf46ff..7ab690e0 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -42,9 +42,9 @@ The example: sudo make install ``` -## How do you run Clixon example commands? +## How do I run Clixon example commands? -- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml` +- Start a backend server: `clixon_backend -F -s init -f /usr/local/etc/example.xml` - Start a cli session: `clixon_cli -f /usr/local/etc/example.xml` - Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml` - Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data` @@ -72,6 +72,82 @@ grep clicon /etc/group clicon:x:1001:,www-data ``` +## How do I use the CLI? + +The easiest way to use Clixon is via the CLI. Once the backend is started +Example: +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## How do I use netconf? + +As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. +Example: +``` +clixon_netconf -qf /usr/local/etc/example.xml +]]>]]> +eth9ex:ethtrue]]>]]> +``` + +However, more useful is to run clixon_netconf as an SSH +subsystem. Register the subsystem in /etc/sshd_config: +``` + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml +``` +and then invoke it from a client using +``` + ssh -s netconf +``` + +## How do I use restconf? + +You can access clixon via REST API using restconf, such as using +curl. GET, PUT, POST are supported. + +You need a web-server, such as nginx, and start a restconf fcgi +daemon, clixon_restconf. + +For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: +``` +server { + ... + location /restconf { + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + } +} +``` +Start nginx daemon +``` +sudo /etc/init.d/nginx start +``` + +Example: +``` + curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type + [ + { + "ietf-interfaces:type": "ex:eth" + } + ] +``` +Read more in the (restconf)[../apps/restconf] docs. ## What about reference documentation? Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. You need to install doxygen and graphviz on your system. @@ -161,59 +237,6 @@ sudo docker run -td olofhagsand/clixon_example ``` Look in the example documentation for more info. -## How do I use netconf? - -As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. -Example: -``` - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml -``` - -However, more useful is to run clixon_netconf as an SSH -subsystem. Register the subsystem in /etc/sshd_config: -``` - Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml -``` -and then invoke it from a client using -``` - ssh -s netconf -``` - -## How do I use restconf? - -You can access clixon via REST API using restconf, such as using -curl. GET, PUT, POST are supported. - -You need a web-server, such as nginx, and start a restconf fcgi -daemon, clixon_restconf. - -For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: -``` -server { - ... - location /restconf { - fastcgi_pass unix:/www-data/fastcgi_restconf.sock; - include fastcgi_params; - } -} -``` -Start nginx daemon -``` -sudo /etc/init.d/nginx start -``` - -Example: -``` - curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type - [ - { - "type": "eth" - } - ] -``` -Read more in the (restconf)[../apps/restconf] docs. - - ## Does Clixon support event streams? Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s. @@ -233,7 +256,7 @@ severity major; or via NETCONF: ``` clixon_netconf -qf /usr/local/etc/example.xml -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> 2018-09-30T12:44:59.657276faultEthernet0major]]>]]> ... @@ -242,11 +265,11 @@ or via restconf: ``` curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE ``` -Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io]. +Consult [clixon restconf](../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as [Nginx Nchan](https://nchan.io). ## How should I start the backend daemon? -There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it: +There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you start the daemon and how loading the configuration affects it: - none - Do not touch running state. Typically after crash when running state and db are synched. - init - Initialize running state. Start with a completely clean running state. - running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists. @@ -277,7 +300,7 @@ There are two ways to add extra XML to running database after start. Note that The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag): ``` - extra + extra ``` You add this via the -c option: @@ -289,12 +312,13 @@ The second way is by programming the plugin_reset() in the backend plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). ## I want to program. How do I extend the example? -See [../apps/example] +See [../apps/example](../apps/example) - example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. - example_cli.cli - Change the fixed part of the CLI commands - example_cli.c - Cli C-commands are placed here. - example_backend.c - Commit and validate functions. +- example_backend_nacm.c - Secondary example plugin (for authorization) - example_netconf.c - Netconf plugin - example_restconf.c - Add restconf authentication, etc. diff --git a/example/README.md b/example/README.md index 7621d9b5..a0a1d621 100644 --- a/example/README.md +++ b/example/README.md @@ -1,19 +1,33 @@ # Clixon example + * [Content](#content) + * [Compile and run](#compile) + * [Using the CLI](#using-the-cli) + * [Using netconf](#using-netconf) + * [Streams](#streams) + * [RPC Operations](#rpc-operations) + * [State data](#state-data) + * [Authentication and NACM](#authentication-and-nacm) + * [Systemd](#systemd) + * [Docker](#docker) + * [Plugins](#plugins) + +## Content + This directory contains a Clixon example which includes a simple example. It contains the following files: -* example.xml The configuration file. See yang/clixon-config@.yang for all available fields. -* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. -* example_cli.cli CLIgen specification. -* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). -* example_backend.c Backend callback plugin including example of: +* `example.xml` The configuration file. See (yang/clixon-config@.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields. +* `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules. +* `example_cli.cli` CLIgen specification. +* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). +* `example_backend.c` Backend callback plugin including example of: * transaction callbacks (validate/commit), * notification, * rpc handler * state-data handler, ie non-config data -* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. -* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback -* example_netconf.c Netconf callback plugin -* Makefile.in Example makefile where plugins are built and installed +* `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically. +* `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback +* `example_netconf.c` Netconf callback plugin +* `Makefile.in` Example makefile where plugins are built and installed ## Compile and run @@ -47,10 +61,37 @@ Send restconf command curl -G http://127.0.0.1/restconf/data ``` -## Setting data example using netconf +## Using the CLI + +The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code. +There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details. + +The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it. +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## Using Netconf + +The following example shows how to set data using netconf: ``` - + eth1 true @@ -65,13 +106,13 @@ Send restconf command ]]>]]> ``` -## Getting data using netconf +### Getting data using netconf ``` ]]>]]> ]]>]]> ]]>]]> -]]>]]> -]]>]]> +eth9ex:eth]]>]]> +]]>]]> ]]>]]> ``` @@ -80,23 +121,134 @@ Send restconf command The example has an EXAMPLE stream notification triggering every 5s. To start a notification stream in the session using netconf, create a subscription: ``` -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> -Routing notification]]>]]> -Routing notification]]>]]> +2019-01-02T10:20:05.929272faultEthernet0major]]>]]> ... ``` This can also be triggered via the CLI: ``` -cli> notify -cli> Routing notification -Routing notification +clixon_cli -f /usr/local/etc/example.xml +cli> notify +cli> event-class fault; +reportingEntity { + card Ethernet0; +} +severity major; ... +cli> no notify +cli> ``` -Restconf support is also supported, see [../apps/restconf/README.md]. +Restconf support is also supported, see (restc)[../apps/restconf/README.md]. -## Initializing a plugin + +## RPC Operations + +Clixon implements Yang RPC operations by an extension mechanism. The +extension mechanism enables you to add application-specific +operations. It works by adding user-defined callbacks for added +netconf operations. It is possible to use the extension mechanism +independent of the yang rpc construct, but it is recommended. The example includes an example: + +Example using CLI: +``` +cli> rpc ipv4 + rpc-reply { + route { + address-family ipv4; + next-hop { + next-hop-list 2.3.4.5; + } + source-protocol static; + } + } +``` +Netconf: +``` +ipv4]]>]]> +ipv42.3.4.5static]]>]]> +``` +Restconf: +``` +curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' +``` + +### Details + +The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)). + +In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data): +``` +static int +fib_route(clicon_handle h, + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ +{ + cprintf(cbret, "" + "ipv4" + "2.3.4.5" + "static" + ""); + return 0; +} +int +clixon_plugin_init(clicon_handle h) +{ +... + rpc_callback_register(h, fib_route, NULL, "fib-route"); +... +} +``` + +## State data + +Netconf and restconf GET also returns state data(not only configuration data). + +In YANG state data is specified with `config false;`. In the example, +`state` is state data, see (example.yang)[example.yang] + +To return state data, you need to write a backend state data callback +with the name "plugin_statedata" where you return an XML tree with +state. This is then merged with config data by the system. + +A static example of returning state data is in the example. Note that +a real example would poll or get the interface counters via a system +call, as well as use the "xpath" argument to identify the requested +state data. + +## Authentication and NACM +The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). +* A NACM backend plugin reporting the mandatory NACM state variables. + +## Systemd + +Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. + +## Docker + +Run the example as a docker container and access it from a host CLI as follows: +``` +ID=$(sudo docker run -td olofhagsand/clixon_example) +IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) +clixon_cli -a IPv4 -u $IP -f ./example.xml +``` + +Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: +``` +make docker +make push +sudo docker run -ti --rm you/clixon_example +``` + +Note that the configuration database is internal in the container, so +it is deleted if the container is restarted. To make the configuration +database persistent, you need to mount running_db using `-v` + +## Plugins The example includes a restonf, netconf, CLI and two backend plugins. Each plugin is initiated with an API struct followed by a plugin init function. @@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h) return &api; /* Return NULL on error */ } ``` - -## Operation data - -Clixon implements Yang RPC operations by an extension mechanism. The -extension mechanism enables you to add application-specific -operations. It works by adding user-defined callbacks for added -netconf operations. It is possible to use the extension mechanism -independent of the yang rpc construct, but it is recommended. The example includes an example: - -Example: -``` -cli> rpc ipv4 - - - -``` - -The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function). -``` - - - ipv4 - - -``` - -In the backend, a callback is registered (fib_route()) which handles the RPC. -``` -static int -fib_route(clicon_handle h, - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* Client session */ - void *regarg) /* Argument given at register */ -{ - cprintf(cbret, ""); - return 0; -} -int -clixon_plugin_init(clicon_handle h) -{ -... - rpc_callback_register(h, fib_route, NULL, "fib-route"); -... -} -``` -## State data - -Netconf and restconf GET also returns state data, in contrast to -config data. -p -In YANG state data is specified with "config false;". In the example, interface-state is state data. - -To return state data, you need to write a backend state data callback -with the name "plugin_statedata" where you return an XML tree with -state. This is then merged with config data by the system. - -A static example of returning state data is in the example. Note that -a real example would poll or get the interface counters via a system -call, as well as use the "xpath" argument to identify the requested -state data. - -## Authentication and NACM -The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): -* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). -* A NACM backend plugin reporting the mandatory NACM state variables. - -## Systemd files - -Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. - -## Docker - -Run the example as a docker container and access it from a host CLI as follows: -``` -ID=$(sudo docker run -td olofhagsand/clixon_example) -IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) -clixon_cli -a IPv4 -u $IP -f ./example.xml -``` - -Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: -``` -make docker -make push -sudo docker run -ti --rm you/clixon_example -``` - -Note that the configuration database is internal in the container, so -it is deleted if the container is restarted. To make the configuration -database persistent, you need to mount running_db using `-v` - - - diff --git a/example/example_backend.c b/example/example_backend.c index 35fe20d4..ae1f51d4 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -96,7 +96,7 @@ example_stream_timer(int fd, int retval = -1; clicon_handle h = (clicon_handle)arg; - /* XXX Change to actual netconf notifications */ + /* XXX Change to actual netconf notifications and namespace */ if (stream_notify(h, "EXAMPLE", "faultEthernet0major") < 0) goto done; if (example_stream_timer_setup(h) < 0) @@ -129,7 +129,7 @@ fib_route(clicon_handle h, /* Clicon handle */ void *arg, /* Client session */ void *regarg) /* Argument given at register */ { - cprintf(cbret, "" + cprintf(cbret, "" "ipv4" "2.3.4.5" "static" @@ -147,7 +147,7 @@ route_count(clicon_handle h, void *arg, void *regarg) /* Argument given at register */ { - cprintf(cbret, "42"); + cprintf(cbret, "42"); return 0; } @@ -180,9 +180,17 @@ example_rpc(clicon_handle h, /* Clicon handle */ { int retval = -1; cxobj *x = NULL; + char *namespace; + /* get namespace from rpc name, return back in each output parameter */ + if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){ + clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe)); + goto done; + } cprintf(cbret, ""); while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (xmlns_set(x, NULL, namespace) < 0) + goto done; if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) goto done; } @@ -222,7 +230,7 @@ example_statedata(clicon_handle h, * Note this state needs to be accomanied by yang snippet * above */ - if (xml_parse_string("" + if (xml_parse_string("" "42" "", NULL, &xstate) < 0) goto done; @@ -251,17 +259,22 @@ example_reset(clicon_handle h, { int retval = -1; cxobj *xt = NULL; + int ret; - if (xml_parse_string("" + if (xml_parse_string("" "loex:loopback" "", NULL, &xt) < 0) goto done; - /* Replace parent w fiorst child */ + /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) + if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0) goto done; + if (ret == 0){ + clicon_err(OE_XML, 0, "Error when writing to XML database"); + goto done; + } retval = 0; done: if (xt != NULL) diff --git a/example/example_cli.c b/example/example_cli.c index 987aae30..01a375a3 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h, goto done; } /* Print result */ - xml_print(stdout, xml_child_i(xret, 0)); + xml2txt(stdout, xml_child_i(xret, 0), 1); retval = 0; done: if (xret) diff --git a/example/example_cli.cli b/example/example_cli.cli index 302a6cf3..892c687a 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{ } copy("Copy and create a new object") { interface("Copy interface"){ - ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); + ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); } } discard("Discard edits (rollback 0)"), discard_changes(); diff --git a/example/example_netconf.c b/example/example_netconf.c index 9993e851..b70128de 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h, void *regarg) { clicon_debug(1, "%s restconf", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/example/example_restconf.c b/example/example_restconf.c index 43771662..ec0e8d27 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h, { // FCGX_Request *r = (FCGX_Request *)arg; clicon_debug(1, "%s", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/include/clixon_custom.h b/include/clixon_custom.h index cbb3f1df..8b7c5ae6 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -51,11 +51,6 @@ int strverscmp (__const char *__s1, __const char *__s2); */ #define XMLNS_YANG_ONLY 1 -/* Set for full XML namespace code in XML, NETCONF and YANG - * Experimental - */ -#undef ENABLE_XMLNS - /* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. * This is a backward compatible fix for 3.9 for CLIgen specification files * using model generation (CLIXON_CLI_GENMODEL). diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 68b19559..0a644680 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -43,6 +43,7 @@ int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json(FILE *f, cxobj *x, int pretty); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); +int json2xml_ns(yang_spec *yspec, cxobj *x, cxobj **xerr); int json_parse_str(char *str, cxobj **xt); int json_parse_file(int fd, yang_spec *yspec, cxobj **xt); diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 8f1b9da0..a615c663 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -52,7 +52,8 @@ int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element); int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message); -int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_namespace(cbuf *cb, char *type, char *namespace, char *message); +int netconf_unknown_namespace_xml(cxobj **xret, char *type, char *namespace, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); int netconf_lock_denied(cbuf *cb, char *info, char *message); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index b5cb55c3..a849c6cd 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -153,6 +153,9 @@ static inline char *clicon_xmldb_dir(clicon_handle h){ static inline char *clicon_xmldb_plugin(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } +static inline int clicon_xml_sort(clicon_handle h){ + return clicon_option_bool(h, "CLICON_XML_SORT"); +} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index cebb0871..dd2eb290 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -86,7 +86,7 @@ int xml_chardata_encode(char **escp, char *fmt, ...); 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 nodeid_split(char *nodeid, char **prefix, char **id); #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 68f332ab..2117bfef 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -45,6 +45,8 @@ * namespace (rfc6241 3.1) */ #define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" +/* default namespace statement, such as in */ +#define DEFAULT_XMLNS "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" /* * Types @@ -84,14 +86,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg); #define XML_FLAG_CHANGE 0x08 /* Node is changed (commits) or child changed rec */ #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ - /* Iterate through modules to find the matching datanode * or rpc if no xmlns attribute specifies namespace. - * This is loose semantics of finding namespaces. + * This is lazy non-strict semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -extern int _CLICON_XML_NS_ITERATE; +extern int _CLICON_XML_NS_STRICT; /* * Prototypes @@ -102,6 +103,7 @@ int xml_name_set(cxobj *xn, char *name); char *xml_prefix(cxobj *xn); int xml_prefix_set(cxobj *xn, char *name); int xml2ns(cxobj *x, char *localname, char **namespace); +int xmlns_set(cxobj *x, char *prefix, char *namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -117,7 +119,9 @@ int xml_type_set(cxobj *xn, enum cxobj_type type); int xml_child_nr(cxobj *xn); int xml_child_nr_type(cxobj *xn, enum cxobj_type type); +int xml_child_nr_notype(cxobj *xn, enum cxobj_type type); cxobj *xml_child_i(cxobj *xn, int i); +cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); @@ -139,6 +143,7 @@ char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); char *xml_find_type_value(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); +cxobj *xml_find_type(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index dba82884..9eba5bfd 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,10 +43,13 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); +int xml_yang_root(cxobj *x, cxobj **xr); +int xmlns_assign(cxobj *x); int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret); int xml_yang_validate_add(cxobj *xt, cbuf *cbret); int xml_yang_validate_all(cxobj *xt, cbuf *cbret); int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret); +int xml_yang_find_non_strict(cxobj *x, yang_spec *yspec, yang_stmt **y); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, @@ -64,8 +67,7 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); int xml_spec_populate(cxobj *x, void *arg); -int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); -int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); +int api_path2xpath(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec, char **reason); diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 620456a0..c4ffeab3 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -37,14 +37,16 @@ #define _CLIXON_XML_SORT_H /* Sort and binary search of XML children - * Experimental + * XXX This variable is a kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ extern int xml_child_sort; /* * Prototypes */ -int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp); +int xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yp); int xml_cmp(const void* arg1, const void* arg2); int xml_sort(cxobj *x0, void *arg); cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); @@ -53,6 +55,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, int xml_sort, yang_stmt *yc); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index e9aee2e6..a9291583 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -252,17 +252,16 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); -int yang_nodeid_split(char *nodeid, char **prefix, char **id); int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace); +yang_stmt *yang_find_module_by_name(yang_spec *yspec, char *name); yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); int yang_order(yang_stmt *y); diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index aa4576df..6ec992c6 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -140,6 +140,7 @@ clicon_err_reset(void) * @param[in] err Error number, typically errno * @param[in] suberr Sub-error number * @param[in] reason Error string, format with argv + * @see clicon_err_reser Resetting the global error variables. */ int clicon_err_fn(const char *fn, diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 06aa71c9..1cd99af1 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -55,10 +55,12 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_queue.h" +#include "clixon_string.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_netconf_lib.h" #include "clixon_json.h" #include "clixon_json_parse.h" @@ -90,43 +92,31 @@ enum childtype{ ANY_CHILD, /* eg or */ }; -/*! Number of children EXCEPT attributes - * @param[in] xn xml node - * @retval number of children in XML tree (except children of type CX_ATTR) - * @see xml_child_nr - */ -static int -xml_child_nr_noattr(cxobj *xn) -{ - cxobj *x = NULL; - int nr = 0; - - while ((x = xml_child_each(xn, x, -1)) != NULL) { - if (xml_type(x) != CX_ATTR) - nr++; - } - return nr; -} - /*! x is element and has exactly one child which in turn has none * remove attributes from x * Clone from clixon_xml_map.c */ static enum childtype -childtype(cxobj *x) +child_type(cxobj *x) { - cxobj *xc1; /* the only child of x */ + cxobj *xc; /* the only child of x */ int clen; /* nr of children */ - clen = xml_child_nr_noattr(x); + clen = xml_child_nr_notype(x, CX_ATTR); if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ if (clen == 0) return NULL_CHILD; if (clen > 1) return ANY_CHILD; - xc1 = xml_child_i(x, 0); /* From here exactly one child */ - if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY) + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -2; /* n/a */ + if (xml_child_nr_notype(xc, CX_ATTR) == 0 && xml_type(xc)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; @@ -175,6 +165,9 @@ arraytype2str(enum array_element_type lt) return ""; } +/*! Check typeof x in array + * Some complexity when x is in different namespaces + */ static enum array_element_type array_eval(cxobj *xprev, cxobj *x, @@ -184,7 +177,10 @@ array_eval(cxobj *xprev, int eqprev=0; int eqnext=0; yang_stmt *ys; + char *nsx; /* namespace of x */ + char *ns2; + nsx = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); if (xml_type(x)!=CX_ELMNT){ array=BODY_ARRAY; goto done; @@ -192,12 +188,18 @@ array_eval(cxobj *xprev, ys = xml_spec(x); if (xnext && xml_type(xnext)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xnext))==0) - eqnext++; + strcmp(xml_name(x),xml_name(xnext))==0){ + ns2 = xml_find_type_value(xnext, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqnext++; + } if (xprev && xml_type(xprev)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xprev))==0) - eqprev++; + strcmp(xml_name(x),xml_name(xprev))==0){ + ns2 = xml_find_type_value(xprev, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqprev++; + } if (eqprev && eqnext) array = MIDDLE_ARRAY; else if (eqprev) @@ -316,8 +318,8 @@ xml2json1_cbuf(cbuf *cb, * module name */ prefix = xml_prefix(x); - if (xml2ns(x, prefix, &namespace) < 0) - goto done; + namespace = xml_find_type_value(x, prefix, "xmlns", CX_ATTR); + if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ yspec = ys_spec(ys); /* Find module name associated with namspace URI */ @@ -325,7 +327,7 @@ xml2json1_cbuf(cbuf *cb, (ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ modname = ymod->ys_argument; } - childt = childtype(x); + childt = child_type(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), @@ -442,7 +444,7 @@ xml2json1_cbuf(cbuf *cb, xc_arraytype, level+1, pretty, 0, bodystr0) < 0) goto done; - if (i%s" "%s" - "%s" + "<%s>%s" "error", - type, tag, element) < 0) + type, tag, infotag, element, infotag) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", message) < 0) @@ -363,7 +364,8 @@ netconf_missing_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "missing-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "missing-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -387,7 +389,8 @@ netconf_missing_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "missing-element", element, message); + return netconf_common_xml(xret, type, "missing-element", + "bad-element", element, message); } /*! Create Netconf bad-element error XML tree according to RFC 6241 App A @@ -408,7 +411,8 @@ netconf_bad_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "bad-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "bad-element", + "bad-element",element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -424,7 +428,7 @@ netconf_bad_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "bad-element", element, message); + return netconf_common_xml(xret, type, "bad-element", "bad-element", element, message); } /*! Create Netconf unknown-element error XML tree according to RFC 6241 App A @@ -444,7 +448,8 @@ netconf_unknown_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "unknown-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "unknown-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -454,13 +459,23 @@ netconf_unknown_element(cbuf *cb, xml_free(xret); return retval; } + +/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A + * + * An unexpected element is present. + * @param[out] xret XML buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] element Bad element name + * @param[in] message Error message + */ int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message) { - return netconf_element_xml_common(xret, type, "unknown-element", element, message); + return netconf_common_xml(xret, type, "unknown-element", + "bad-element", element, message); } /*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A @@ -474,35 +489,32 @@ netconf_unknown_element_xml(cxobj **xret, int netconf_unknown_namespace(cbuf *cb, char *type, - char *info, + char *namespace, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "unknown-namespace" - "%s" - "error", - type, info) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_common_xml(&xret, type, "unknown-namespace", + "bad-namespace", namespace, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} + +int +netconf_unknown_namespace_xml(cxobj **xret, + char *type, + char *namespace, + char *message) +{ + return netconf_common_xml(xret, type, "unknown-namespace", + "bad-namespace", namespace, message); } /*! Create Netconf access-denied error cbuf according to RFC 6241 App A @@ -989,7 +1001,7 @@ netconf_trymerge(cxobj *x, } /*! Load ietf netconf yang module and set enabled features - * The features added are: + * The features added are (in order): * candidate (8.3) * validate (8.6) * startup (8.7) diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 45c3948b..e6410ad8 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -253,9 +253,8 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = 1; -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; @@ -281,9 +280,8 @@ clicon_options_main(clicon_handle h, xml_child_sort = 1; else xml_child_sort = 0; -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE"); -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index aa3ac4ac..396a0665 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -351,7 +351,7 @@ clicon_rpc_edit_config(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); @@ -787,7 +787,7 @@ clicon_rpc_create_subscription(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("" + if ((msg = clicon_msg_encode("" "%s" "" "", diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 480805fb..a7c05f39 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -562,6 +562,54 @@ clicon_str2int(const map_str2int *mstab, return -1; } +/*! Split colon-separated node identifier into prefix and name + * @param[in] node-id + * @param[out] prefix Malloced string. May be NULL. + * @param[out] id Malloced identifier. + * @retval 0 OK + * @retval -1 Error + * @code + * char *prefix = NULL; + * char *id = NULL; + * if (nodeid_split(nodeid, &prefix, &id) < 0) + * goto done; + * if (prefix) + * free(prefix); + * if (id) + * free(id); + * @note caller need to free id and prefix after use + */ +int +nodeid_split(char *nodeid, + char **prefix, + char **id) +{ + int retval = -1; + char *str; + + if ((str = strchr(nodeid, ':')) == NULL){ + if ((*id = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + else{ + if ((*prefix = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + (*prefix)[str-nodeid] = '\0'; + str++; + if ((*id = strdup(str)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! strndup() for systems without it, such as xBSD */ #ifndef HAVE_STRNDUP diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 59292ba9..6849e94d 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -62,6 +62,8 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" /* xml_spec_populate */ +#include "clixon_xml_map.h" /* xml_spec_populate */ #include "clixon_xml_sort.h" #include "clixon_xml_parse.h" @@ -133,9 +135,9 @@ struct xml{ * or rpc if no xmlns attribute specifies namespace. * This is loose semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -int _CLICON_XML_NS_ITERATE = 0; +int _CLICON_XML_NS_STRICT = 1; /* Mapping between xml type <--> string */ static const map_str2int xsmap[] = { @@ -226,8 +228,7 @@ xml_prefix_set(cxobj *xn, return 0; } - -/*! Given an xml tree return URI namespace: default or localname given +/*! Given an xml tree return URI namespace recursively : default or localname given * * Given an XML tree and a prefix (or NULL) return URI namespace. * @param[in] x XML tree @@ -235,7 +236,7 @@ xml_prefix_set(cxobj *xn, * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree * @retval 0 OK * @retval -1 Error - * @see xmlns_check XXX coordinate + * @see xmlns_check XXX can these be merged? */ int xml2ns(cxobj *x, @@ -246,9 +247,9 @@ xml2ns(cxobj *x, char *ns; cxobj *xp; - if (prefix != NULL) /* xmlns: */ + if (prefix != NULL) /* xmlns:="" */ ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); - else /* default ns */ + else /* xmlns="" */ ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); /* namespace not found, try parent */ @@ -270,6 +271,40 @@ xml2ns(cxobj *x, return retval; } +/*! Add a namespace attribute to an XML node, either default or specific prefix + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then set default xmlns + * @param[out] namespace URI namespace (or NULL). Will be copied + * @retval 0 OK + * @retval -1 Error + * @see xml2ns + */ +int +xmlns_set(cxobj *x, + char *prefix, + char *namespace) +{ + int retval = -1; + cxobj *xa; + + if (prefix != NULL){ /* xmlns:="" */ + if ((xa = xml_new(prefix, x, NULL)) == NULL) + goto done; + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + } + else{ /* xmlns="" */ + if ((xa = xml_new("xmlns", x, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + } + if (xml_value_set(xa, namespace) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! See if xmlns:[=] exists, if so return * * @param[in] xn XML node @@ -483,6 +518,8 @@ xml_type_set(cxobj *xn, /*! Get number of children * @param[in] xn xml node * @retval number of children in XML tree + * @see xml_child_nr_type + * @see xml_child_nr_notype */ int xml_child_nr(cxobj *xn) @@ -490,10 +527,33 @@ xml_child_nr(cxobj *xn) return xn->x_childvec_len; } +/*! Get number of children of EXCEPT specific type + * @param[in] xn xml node + * @param[in] type XML type or -1 for all + * @retval number of typed children in XML tree (except type) + * @see xml_child_nr + * @see xml_child_nr_type + */ +int +xml_child_nr_notype(cxobj *xn, + enum cxobj_type type) +{ + cxobj *x = NULL; + int nr = 0; + + while ((x = xml_child_each(xn, x, -1)) != NULL) { + if (xml_type(x) != type) + nr++; + } + return nr; +} + /*! Get number of children of specific type * @param[in] xn xml node * @param[in] type XML type or -1 for all * @retval number of typed children in XML tree + * @see xml_child_nr + * @see xml_child_nr_notype */ int xml_child_nr_type(cxobj *xn, @@ -521,6 +581,28 @@ xml_child_i(cxobj *xn, return NULL; } +/*! Get a specific child of a specific type + * @param[in] xn xml node + * @param[in] i the number of the child of specific type + * @param[in] type Child type + * @retval child in XML tree, or NULL if no such child, or empty child + * @see xml_child_i + */ +cxobj * +xml_child_i_type(cxobj *xn, + int i, + enum cxobj_type type) +{ + cxobj *x = NULL; + int it = 0; + + while ((x = xml_child_each(xn, x, type)) != NULL) { + if (x->x_type == type && (i == it++)) + return x; + } + return NULL; +} + /*! Set specific child * @param[in] xn xml node * @param[in] i the number of the child, eg order in children vector @@ -938,7 +1020,7 @@ xml_body_get(cxobj *xt) return NULL; } -/*! Find and return the value of an xml child of specific type +/*! Find and return the value of an xml child of specific type given prefix and name * * The value can be of an attribute only * @param[in] xt xml tree node @@ -950,6 +1032,7 @@ xml_body_get(cxobj *xt) * char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR); * @endcode * @note, make a copy of the return value to use it properly + * @see xml_find_type return the xml object * @see xml_find_value where a body can be found as well */ char * @@ -957,6 +1040,32 @@ xml_find_type_value(cxobj *xt, char *prefix, char *name, enum cxobj_type type) +{ + cxobj *x; + + if ((x = xml_find_type(xt, prefix, name, type)) != NULL) + return xml_value(x); + return NULL; +} + +/*! Find and return the xml child of specific type given prefix and name + * + * The value can be of an attribute only + * @param[in] xt xml tree node + * @param[in] prefix Prefix (namespace local name) or NULL + * @param[in] name name of xml tree node (eg attr name or "body") + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node + * @code + * cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR); + * @endcode + * @see xml_find_value where a body can be found as well + */ +cxobj * +xml_find_type(cxobj *xt, + char *prefix, + char *name, + enum cxobj_type type) { cxobj *x = NULL; int pmatch; /* prefix match */ @@ -969,7 +1078,7 @@ xml_find_type_value(cxobj *xt, else pmatch = 1; if (pmatch && strcmp(name, xml_name(x)) == 0) - return xml_value(x); + return x; } return NULL; } @@ -1397,6 +1506,8 @@ _xml_parse(const char *str, goto done; /* Sort the complete tree after parsing */ if (yspec){ + if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) goto done; if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) @@ -1592,7 +1703,7 @@ xml_parse_va(cxobj **xtop, return retval; } -/*! Copy single xml node without copying children +/*! Copy single xml node frm x0 to x1 without copying children */ int xml_copy_one(cxobj *x0, diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index c3a4126a..32aab817 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -380,7 +380,8 @@ xmldb_get(clicon_handle h, * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". - * @retval 0 OK + * @retval 1 OK + * @retval 0 Failed, cbret contains error xml message * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code @@ -388,8 +389,10 @@ xmldb_get(clicon_handle h, * cxobj *xret = NULL; * if (xml_parse_string("17", yspec, &xt) < 0) * err; - * if (xmldb_put(xh, "running", OP_MERGE, xt, cbret) < 0) + * if ((ret = xmldb_put(xh, "running", OP_MERGE, xt, cbret)) < 0) * err; + * if (ret==0) + * cbret contains netconf error message * @endcode * @note that you can add both config data and state data. In comparison, * xmldb_get has a parameter to get config data only. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2644f494..5d210644 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -50,6 +50,7 @@ * +---------+ * | file | * +---------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -95,14 +96,20 @@ static int tleaf(cxobj *x) { - cxobj *c; + cxobj *xc; if (xml_type(x) != CX_ELMNT) return 0; - if (xml_child_nr(x) != 1) + if (xml_child_nr_notype(x, CX_ATTR) != 1) return 0; - c = xml_child_i(x, 0); - return (xml_child_nr(c) == 0); + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -1; /* n/a */ + return (xml_child_nr_notype(xc, CX_ATTR) == 0); } /*! Translate XML -> TEXT @@ -114,39 +121,42 @@ xml2txt(FILE *f, cxobj *x, int level) { - cxobj *xe = NULL; + cxobj *xc = NULL; int children=0; - char *term = NULL; int retval = -1; - xe = NULL; /* count children */ - while ((xe = xml_child_each(x, xe, -1)) != NULL) - children++; - if (!children){ - if (xml_type(x) == CX_BODY){ - term = xml_value(x); + xc = NULL; /* count children (elements and bodies, not attributes) */ + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + children++; + if (!children){ /* If no children print line */ + switch (xml_type(x)){ + case CX_BODY: + fprintf(f, "%s;\n", xml_value(x)); + break; + case CX_ELMNT: + fprintf(f, "%*s;\n", 4*level, xml_name(x)); + break; + default: + break; } - else{ - fprintf(f, "%*s", 4*level, ""); - term = xml_name(x); - } - fprintf(f, "%s;\n", term); - retval = 0; - goto done; + goto ok; } fprintf(f, "%*s", 4*level, ""); fprintf(f, "%s ", xml_name(x)); if (!tleaf(x)) fprintf(f, "{\n"); - xe = NULL; - while ((xe = xml_child_each(x, xe, -1)) != NULL){ - if (xml2txt(f, xe, level+1) < 0) - break; + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + if (xml2txt(f, xc, level+1) < 0) + break; } if (!tleaf(x)) fprintf(f, "%*s}\n", 4*level, ""); + ok: retval = 0; - done: + // done: return retval; } @@ -172,7 +182,10 @@ xml2cli(FILE *f, int match; char *body; - ys = xml_spec(x); + if (xml_type(x)==CX_ATTR) + goto ok; + if ((ys = xml_spec(x)) == NULL) + goto ok; if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); @@ -361,6 +374,37 @@ validate_identityref(cxobj *xt, goto done; } +/*! Given an XML node, return root node + * A root node is an ancestor xr of x with one or both of the following properties + * - its XML parent is NULL parent, + * - its associated yang specification's parent is a yang module. + * @param[in] x XML node + * @param[out] xr XML root + */ +int +xml_yang_root(cxobj *x, + cxobj **xr) +{ + int retval = -1; + cxobj *xp; + yang_stmt *y; + yang_stmt *yp; + + while ((xp = xml_parent(x)) != NULL){ + if ((y = xml_spec(x)) != NULL && + (yp = (yang_stmt*)y->ys_parent) != NULL) + /* Actually, maybe only the Y_MODULE clause is relevant */ + if (yp==NULL || + yp->ys_keyword == Y_MODULE || + yp->ys_keyword == Y_SUBMODULE) + break; /* x is the root */ + x = xp; + } + *xr = x; + retval = 0; + return retval; +} + /*! Validate an RPC node * @param[in] xt XML node to be validated * @retval 1 Validation OK @@ -408,7 +452,6 @@ xml_yang_validate_rpc(cxobj *xrpc, int retval = -1; yang_stmt *yn=NULL; /* rpc name */ cxobj *xn; /* rpc name */ - int ret; if (strcmp(xml_name(xrpc), "rpc")){ clicon_err(OE_XML, EINVAL, "Expected RPC"); @@ -417,16 +460,15 @@ xml_yang_validate_rpc(cxobj *xrpc, xn = NULL; /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { - if ((yn = xml_spec(xn)) == NULL) - goto fail; - if ((ret = xml_yang_validate_all(xn, cbret)) < 0) - goto fail; - if (ret == 0) - goto fail; - if ((ret = xml_yang_validate_add(xn, cbret)) < 0) - goto fail; - if (ret == 0) + if ((yn = xml_spec(xn)) == NULL){ + if (netconf_unknown_element(cbret, "application", xml_name(xn), NULL) < 0) + goto done; goto fail; + } + if ((retval = xml_yang_validate_all(xn, cbret)) < 1) + goto done; /* error or validation fail */ + if ((retval = xml_yang_validate_add(xn, cbret)) < 1) + goto done; /* error or validation fail */ if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0) goto done; } @@ -443,9 +485,9 @@ xml_yang_validate_rpc(cxobj *xrpc, * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -456,6 +498,7 @@ xml_yang_validate_rpc(cxobj *xrpc, * fail; * @endcode * @see xml_yang_validate_all + * @see xml_yang_validate_rpc * @note Should need a variant accepting cxobj **xret */ int @@ -544,9 +587,9 @@ xml_yang_validate_add(cxobj *xt, /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -680,6 +723,8 @@ xml_yang_validate_all(cxobj *xt, goto done; } +/*! Translate a single xml node to a cligen variable vector. Note not recursive + */ int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret) @@ -695,6 +740,47 @@ xml_yang_validate_all_top(cxobj *xt, return 1; } +/*! Given XML node x, find yang spec in _any_ module matching name + * This is non-struct namespace semantics (not correct) but necessary + * in historic Clixon code. + * Also, add a proper default namespaces statement (xmlns="uri") in x + * @param[in] x XML node (find yang statement on this one) + * @param[in] yspec Top-level yang spec + * @param[out] y Yang stmt associated to x. NULL i not found + * @retval 0 OK + * @see CLICON_XML_NS_STRICT clixon config option + */ +int +xml_yang_find_non_strict(cxobj *x, + yang_spec *yspec, + yang_stmt **yp) +{ + int retval = -1; + char *name; + yang_stmt *ymod; + int i; + yang_stmt *y=NULL; + char *ns; + + name = xml_name(x); + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) + break; + } + if (y){ + *yp = y; + if ((ns = yang_find_mynamespace(ymod)) != NULL){ + if (xml_find_type_value(x, NULL, "xmlns", CX_ATTR) == NULL) + if (xmlns_set(x, NULL, ns) < 0) + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt @@ -861,28 +947,6 @@ cvec2xml_1(cvec *cvv, return retval; } - -/*! Find next yang node, either start from yang_spec or some yang-node - * @param[in] y Node spec or sny yang-node - * @param[in] name Name of childnode to find - * @retval ys yang statement - * @retval NULL Error: no node found - */ -static yang_stmt * -yang_next(yang_node *y, - char *name) -{ - yang_stmt *ys; - - if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); - else - ys = yang_find_datanode(y, name); - if (ys == NULL) - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - return ys; -} - /*! Recursive help function to compute differences between two xml trees * @param[in] x1 First XML tree * @param[in] x2 Second XML tree @@ -920,9 +984,12 @@ xml_diff1(yang_stmt *ys, */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL) + if ((yc = xml_spec(x1c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; - if (match_base_child(x2, x1c, &x2c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x2, x1c, &x2c, xml_child_sort, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) @@ -954,9 +1021,12 @@ xml_diff1(yang_stmt *ys, */ x2c = NULL; while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL) + if ((yc = xml_spec(x2c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; - if (match_base_child(x1, x2c, &x1c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x1, x2c, &x1c, xml_child_sort, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1025,10 +1095,11 @@ xml_diff(yang_spec *yspec, * Recursively construct it to the top. * Example: * yang: container a -> list b -> key c -> leaf d - * xpath: /a/b/%s/d + * xpath: /modname:a/b/%s/d * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] cb api_path_fmt, + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ static int yang2api_path_fmt_1(yang_stmt *ys, @@ -1040,28 +1111,35 @@ yang2api_path_fmt_1(yang_stmt *ys, cvec *cvk = NULL; /* vector of index keys */ int retval = -1; - yp = ys->ys_parent; - if (yp != NULL && + if ((yp = ys->ys_parent) == NULL){ + clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument); + goto done; + } + if (yp != NULL && /* XXX rm */ yp->yn_keyword != Y_MODULE && yp->yn_keyword != Y_SUBMODULE){ - if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) + if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; + cprintf(cb, "/"); } + else /* top symbol - mark with name prefix */ + cprintf(cb, "/%s:", yp->yn_argument); + if (inclkey){ if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } else{ #if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) - cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ + cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else #endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } switch (ys->ys_keyword){ @@ -1095,6 +1173,7 @@ yang2api_path_fmt_1(yang_stmt *ys, * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] api_path_fmt XML api path. Needs to be freed after use. + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int yang2api_path_fmt(yang_stmt *ys, @@ -1148,6 +1227,8 @@ yang2api_path_fmt(yang_stmt *ys, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * api_path: /subif-entry=foo/subid + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2api_path(char *api_path_fmt, @@ -1211,7 +1292,6 @@ api_path_fmt2api_path(char *api_path_fmt, return retval; } - /*! Transform an xml key format and a vector of values to an XML path * Used to input xmldb_get() or xmldb_get_vec * @param[in] api_path_fmt XML key format @@ -1230,6 +1310,8 @@ api_path_fmt2api_path(char *api_path_fmt, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * xpath: /subif-entry[if-name=foo]/subid" + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2xpath(char *api_path_fmt, @@ -1470,6 +1552,9 @@ xml_default(cxobj *xt, /*! Order XML children according to YANG * @param[in] xt XML top of tree + * @param[in] arg Dummy (so it can be called from xml_apply) + * @see xml_sort XXX: how do they relate? + * Called from text_get *only* */ int xml_order(cxobj *xt, @@ -1603,9 +1688,7 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *y=NULL; /* yang node */ yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ - // yang_stmt *ya = NULL; /* arg */ cxobj *x; - // cxobj *xi; int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ @@ -1621,7 +1704,7 @@ xml_spec_populate_rpc(clicon_handle h, /* Loose semantics: loop through all modules to find the node */ if (y == NULL && - clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){ + !clicon_option_bool(h, "CLICON_XML_NS_STRICT")){ for (i=0; iyp_len; i++){ ymod = yspec->yp_stmt[i]; if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) @@ -1633,16 +1716,8 @@ xml_spec_populate_rpc(clicon_handle h, if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ /* kludge rpc -> input */ xml_spec_set(x, yi); -#if 1 if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; -#else - xi = NULL; - while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { - if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) - xml_spec_set(xi, ya); - } -#endif } } } @@ -1651,13 +1726,14 @@ xml_spec_populate_rpc(clicon_handle h, return retval; } + /*! Add yang specification backpointer to XML node * @param[in] xt XML tree node * @param[in] arg Yang spec * @note This may be unnecessary if yspec is set on creation * @note For subs to anyxml nodes will not have spec set * @note No validation is done,... XXX - * @note relies on kludge _CLICON_XML_NS_ITERATE + * @note relies on kludge _CLICON_XML_NS_STRICT * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode @@ -1674,7 +1750,6 @@ xml_spec_populate(cxobj *x, yang_stmt *ymod; /* yang module */ cxobj *xp; /* xml parent */ char *name; - int i; yspec = (yang_spec*)arg; if (xml_spec(x)) @@ -1688,15 +1763,12 @@ xml_spec_populate(cxobj *x, goto done; if (ymod != NULL) y = yang_find_datanode((yang_node*)ymod, name); - /* Loose semantics: loop through all modules to find the node - * XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE") + /* Non-strict semantics: loop through all modules to find the node + * XXX clicon_option_bool(h, "CLICON_XML_NS_STRICT") */ - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) + goto done; } } if (y) @@ -1713,15 +1785,19 @@ xml_spec_populate(cxobj *x, return retval; } - /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] - * @param[in] yspec Yang spec - * @param[in] pcvec api-path as cvec - * @param[in] pi Offset of cvec, where api-path starts - * @param[out] path The xpath as cbuf variable string, must be initializeed - * The api-path has some wierd encoding, use api_path2xpath() if you are - * confused. + * eg example:a/b -> ex:a/b + * @param[in] yspec Yang spec + * @param[in] cvv api-path as cvec + * @param[in] offset Offset of cvec, where api-path starts + * @param[out] xpath The xpath as cbuf variable string, must be initializeed + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * + * @note both retval 0 and -1 set clicon_err, but the later is fatal + * @note Not proper namespace translation from api-path 2 xpath * It works like this: * Assume origin incoming path is * "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is: @@ -1729,46 +1805,63 @@ xml_spec_populate(cxobj *x, * which means the api-path is ["a" "b=c"] corresponding to "a/b=c" * @code * cbuf *xpath = cbuf_new(); - * if (api_path2xpath_cvv(yspec, cvv, i, xpath) + * cvec *cvv = NULL; + * if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0) * err; + * if ((ret = api_path2xpath(yspec, cvv, 0, cxpath)) < 0) + * err; + * if (ret == 0){ + * ... access error string in clicon_err_reason + * clicon_err_reset(); + * return; + * } * ... access xpath as cbuf_get(xpath) * cbuf_free(xpath) * @endcode - * @see api_path2xpath for string api-path argument + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xml For api-path to xml tree */ int -api_path2xpath_cvv(yang_spec *yspec, - cvec *cvv, - int offset, - cbuf *xpath) +api_path2xpath(yang_spec *yspec, + cvec *cvv, + int offset, + cbuf *xpath) { int retval = -1; int i; cg_var *cv; - char *name; + char *nodeid; + char *prefix = NULL; + char *name = NULL; cvec *cvk = NULL; /* vector of index keys */ yang_stmt *y = NULL; + yang_stmt *ymod; char *val; char *v; cg_var *cvi; for (i=offset; iyn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, nodeclass); - } - else { - y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): - yang_find_datanode((yang_node*)y0, name); - } - if (y == NULL){ - clicon_err(OE_YANG, errno, "No yang node found: %s", name); + /* Split into prefix and localname */ + if (nodeid_split(nodeid, &prefix, &name) < 0) goto done; + if (y0->yn_keyword == Y_SPEC){ /* top-node */ + if (prefix == NULL){ + clicon_err(OE_XML, EINVAL, "api-path element '%s', expected prefix:name", nodeid); + goto fail; + } + if ((ymod = yang_find_module_by_name((yang_spec*)y0, prefix)) == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix); + goto fail; + } + namespace = yang_find_mynamespace(ymod); + y0 = (yang_node*)ymod; + + } + y = (nodeclass==YC_SCHEMANODE)? + yang_find_schemanode((yang_node*)y0, name): + yang_find_datanode((yang_node*)y0, name); + if (y == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name); + goto fail; } switch (y->ys_keyword){ case Y_LEAF_LIST: if (0 && restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '='"); + clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); goto done; } if ((x = xml_new(y->ys_argument, x0, y)) == NULL) @@ -1924,21 +2014,21 @@ api_path2xml_vec(char **vec, } if (restval==NULL){ // XXX patch to allow for lists without restval to be backward compat - // clicon_err(OE_XML, 0, "malformed key, expected '='"); + // clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); // goto done; } else{ if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) goto done; if (nvalvec > cvec_len(cvk)){ - clicon_err(OE_XML, errno, "List %s key length mismatch", name); - goto done; + clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name); + goto fail; } } cvi = NULL; /* create list object */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); j = 0; /* Create keys */ @@ -1957,31 +2047,47 @@ api_path2xml_vec(char **vec, break; default: /* eg Y_CONTAINER, Y_LEAF */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); break; } - if (api_path2xml_vec(vec+1, nvec-1, - x, (yang_node*)y, - nodeclass, - xpathp, ypathp) < 0) + if (x && namespace){ + if (xmlns_set(x, NULL, namespace) < 0) + goto done; + } + if ((retval = api_path2xml_vec(vec+1, nvec-1, + x, (yang_node*)y, + nodeclass, + xpathp, ypathp)) < 1) goto done; - retval = 0; + ok: + retval = 1; /* OK */ done: + if (prefix) + free(prefix); + if (name) + free(name); if (restval) free(restval); if (valvec) free(valvec); return retval; + fail: + retval = 0; /* invalid api-path or XML */ + goto done; } /*! Create xml tree from api-path - * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] api_path (Absolute) API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * @note both retval 0 and -1 set clicon_err, but the later is fatal * @example * api_path: /subif-entry=foo/subid * xtop[in] @@ -1990,7 +2096,8 @@ api_path2xml_vec(char **vec, * * xbotp: * ybotp: Y_LEAF subid - * @see api_path2xml_vec + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xpath For api-path to xml xpath translation */ int api_path2xml(char *api_path, @@ -2003,10 +2110,13 @@ api_path2xml(char *api_path, int retval = -1; char **vec = NULL; int nvec; + cxobj *xroot; + clicon_debug(1, "%s", __FUNCTION__); if (*api_path!='/'){ - clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path); - goto ok; + clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')", + api_path); + goto fail; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; @@ -2014,19 +2124,70 @@ api_path2xml(char *api_path, if (nvec > 1 && !strlen(vec[nvec-1])) nvec--; if (nvec < 1){ - clicon_err(OE_XML, 0, "Malformed key: %s", api_path); - goto done; + clicon_err(OE_XML, EINVAL, "Malformed api-path: %s", api_path); + goto fail; } nvec--; /* NULL-terminated */ - if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, nodeclass, - xbotp, ybotp) < 0) + if ((retval = api_path2xml_vec(vec+1, nvec, + xtop, (yang_node*)yspec, nodeclass, + xbotp, ybotp)) < 1) + goto done; + xml_yang_root(*xbotp, &xroot); + if (xmlns_assign(xroot) < 0) + goto done; + // ok: + retval = 1; + done: + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if the module tree x is in is assigned right XML namespace, assign if not + * @param[in] x XML node + *(0. You should probably find the XML root and apply this function to that.) + * 1. Check which namespace x should have (via yang). This is correct namespace. + * 2. Check which namespace x has via its XML tree + * 3. If equal, OK + * 4. Assign the correct namespace to the XML node + * Assign default namespace to x. Create an "xmlns" + * @code + * xml_yang_root(x, &xroot); + * xmlns_assign(xroot); + * @endcode + */ +int +xmlns_assign(cxobj *x) +{ + int retval = -1; + yang_stmt *y; + char *ns_correct; /* correct uri */ + char *ns_xml; /* may be null or incorrect */ + + if ((y = xml_spec(x)) == NULL){ + clicon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x)); + goto done; + } + /* 1. Check which namespace x should have (via yang). This is correct namespace. */ + if ((ns_correct = yang_find_mynamespace(y)) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang node %s does not have namespace", y->ys_argument); + goto done; + } + /* 2. Check which namespace x has via its XML tree */ + if (xml2ns(x, NULL, &ns_xml) < 0) + goto done; + /* 3. If equal, OK, 4. Else, find root of XML tree */ + if (ns_xml && strcmp(ns_xml, ns_correct)==0) + goto ok; + /* 4. Assign the correct namespace */ + if (xmlns_set(x, NULL, ns_correct) < 0) goto done; ok: retval = 0; done: - if (vec) - free(vec); return retval; } @@ -2053,6 +2214,8 @@ xml_merge1(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ + cxobj *x0a; /* x0 xmlns attribute */ + cxobj *x1a; /* x1 xmlns attribute */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ @@ -2109,7 +2272,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2117,6 +2280,17 @@ xml_merge1(cxobj *x0, goto ok; } } /* else Y_CONTAINER */ + assert(x0); + /* Copy xmlns attributes (if it does not already exist) */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_find_type(x0, NULL, "xmlns", CX_ATTR)==NULL){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } + + ok: retval = 0; done: @@ -2147,20 +2321,31 @@ xml_merge(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; + yang_stmt *ymod; cbuf *cbr = NULL; /* Reason buffer */ /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + if ((ys_module_by_xml(yspec, x1c, &ymod)) < 0) + goto done; + if (ymod == NULL){ + if (reason && + (*reason = strdup("No namespace in XML tree found")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + goto ok; + } /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ + if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) == NULL){ if (reason){ if ((cbr = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); + cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?)", xml_name(x1), x1cname); if ((*reason = strdup(cbuf_get(cbr))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; @@ -2169,13 +2354,14 @@ xml_merge(cxobj *x0, break; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; if (*reason != NULL) break; } + ok: retval = 0; /* OK */ done: if (cbr) diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 7600d249..d6a1200f 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -130,6 +130,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, * @param[in] ya XML parser yacc handler struct * @param[in] prefix Prefix, namespace, or NULL * @param[in] localpart Name + * @note the call to xml_child_spec() may not have xmlns attribute read yet XXX */ static int xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, @@ -138,12 +139,14 @@ xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *x; yang_stmt *y = NULL; /* yang node */ - cxobj *xp; /* xml parent */ + cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; ya->ya_xelement = x; retval = 0; @@ -168,9 +171,11 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; if (xml_prefix_set(x, prefix) < 0) goto done; @@ -313,19 +318,15 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, char *attval) { int retval = -1; - cxobj *xa; + cxobj *xa = NULL; -#ifdef ENABLE_XMLNS - if (prefix && strcmp(prefix,"xmlns")==0) - fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); - if (prefix==NULL && strcmp(name,"xmlns")==0) - fprintf(stderr, "DefaultAttName = %s\n", attval); -#endif /* notyet */ - if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (prefix && xml_prefix_set(xa, prefix) < 0) - goto done; + if ((xa = xml_find_type(ya->ya_xelement, prefix, name, CX_ATTR)) == NULL){ + if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (prefix && xml_prefix_set(xa, prefix) < 0) + goto done; + } if (xml_value_set(xa, attval) < 0) goto done; retval = 0; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index befb5bcb..265d82a5 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -61,6 +61,8 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_xml_map.h" #include "clixon_xml_sort.h" /* @@ -68,7 +70,9 @@ */ /* Sort and binary search of XML children - * Experimental + * XXX kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ int xml_child_sort = 1; @@ -83,7 +87,7 @@ int xml_child_sort = 1; * xmlns and xmlns:ns are used. */ int -xml_child_spec(char *name, +xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yresult) @@ -93,8 +97,9 @@ xml_child_spec(char *name, yang_stmt *yparent; /* parent yang */ yang_stmt *ymod = NULL; yang_stmt *yi; - int i; + char *name; + name = xml_name(x); if (xp && (yparent = xml_spec(xp)) != NULL){ if (yparent->ys_keyword == Y_RPC){ if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL) @@ -108,12 +113,9 @@ xml_child_spec(char *name, goto done; if (ymod != NULL) y = yang_find_schemanode((yang_node*)ymod, name); - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) /* schemanode */ + goto done; } } else @@ -265,6 +267,7 @@ xml_cmp1(cxobj *x, * Assume populated by yang spec. * @param[in] x0 XML node * @param[in] arg Dummy so it can be called by xml_apply() + * @see xml_order XXX: how do they relate? */ int xml_sort(cxobj *x, @@ -560,9 +563,10 @@ xml_sort_verify(cxobj *x0, } /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child + * param[in] x0 Base tree node + * param[in] x1c Modification tree child + * param[in] yc Yang spec of tree child + * param[in] xml_sort Value of CLICON_XML_SORT option * param[out] x0cp Matching base tree child (if any) * @note XXX: room for optimization? on 1K calls we have 1M body calls and 500K xml_child_each/cvec_each calls. @@ -575,6 +579,7 @@ int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, + int xml_sort, yang_stmt *yc) { int retval = -1; @@ -632,7 +637,7 @@ match_base_child(cxobj *x0, break; } /* Get match. Sorting mode(optimized) or not?*/ - if (xml_child_sort==0) + if (xml_sort==0) *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); else{ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ @@ -640,12 +645,6 @@ match_base_child(cxobj *x0, *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); } else{ -#if 1 /* This is just a warning, but a catcher for when xml tree is not - populated with yang spec. If you see this, a previous invacation of, - for example xml_spec_populate() may be missing - */ - clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); -#endif *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } } diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 68c9ce9f..2298235a 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -469,94 +469,6 @@ yang_match(yang_node *yn, } return match; } -#ifdef NOTYET -/*! Prototype more generic than yang_find_datanode and yang_find_schemanode - */ -yang_stmt * -yang_find_class(yang_node *yn, - char *argument, - yang_class class) -{ - yang_stmt *ys = NULL; - yang_stmt *yc = NULL; - yang_stmt *ysmatch = NULL; - int i, j; - int ok; - - for (i=0; iyn_len; i++){ - ys = yn->yn_stmt[i]; - switch(class){ - case YC_NONE: - ok = 1; - break; - case YC_DATANODE: - ok = yang_datanode(ys); - break; - case YC_DATADEFINITION: - ok = yang_datadefinition(ys); - break; - case YC_SCHEMANODE: - ok = yang_schemanode(ys); - break; - } - if (!ok) - continue; - switch(class){ - case YC_NONE: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_DATANODE: - case YC_DATADEFINITION: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_SCHEMANODE: - if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ - for (j=0; jys_len; j++){ - yc = ys->ys_stmt[j]; - if (yc->ys_keyword == Y_CASE) /* Look for its children */ - ysmatch = yang_find_class((yang_node*)yc, argument, class); - else{ - if (yang_schemanode(yc)){ - if (argument == NULL) - ysmatch = yc; - else - if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) - ysmatch = yc; - } - } - if (ysmatch) - goto match; - } - } /* Y_CHOICE */ - else{ - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - - } - break; - } /* switch */ - } /* for */ - match: - return ysmatch; -} -#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -658,69 +570,6 @@ yang_find_schemanode(yang_node *yn, return ysmatch; } -/*! Find first matching data node in all modules in a yang spec (prefixes) - * - * @param[in] ysp Yang specification - * @param[in] nodeid Name of node. If NULL match first - * @param[in] class See yang_class for class of yang nodes - * A yang specification has modules as children which in turn can have - * syntax-nodes as children. This function goes through all the modules to - * look for nodes. Note that if a child to a module is a choice, - * the search is made recursively made to the choice's children. - * @note works for import prefix, but not work for generic XML parsing where - * xmlns and xmlns:ns are used. - * @see yang_find_top_ns - */ -yang_stmt * -yang_find_topnode(yang_spec *ysp, - char *nodeid, - yang_class class) -{ - yang_stmt *ymod = NULL; /* module */ - yang_stmt *yres = NULL; /* result */ - char *prefix = NULL; - char *id = NULL; - int i; - - if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - goto done; - if (prefix){ - if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL || - (ymod = yang_find((yang_node*)ysp, Y_SUBMODULE, prefix)) != NULL){ - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - goto done; - } - } - else /* No prefix given - loop through and find first */ - for (i=0; iyp_len; i++){ - ymod = ysp->yp_stmt[i]; - switch (class){ - case YC_NONE: - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - break; - case YC_DATANODE: - if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_SCHEMANODE: - if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_DATADEFINITION: - break; /* nyi */ - } - } - ok: - done: - if (prefix) - free(prefix); - if (id) - free(id); - return yres; -} - /*! Given a yang statement, find the prefix associated to this module * @param[in] ys Yang statement in module tree (or module itself) * @retval NULL Not found @@ -775,7 +624,6 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } - /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -972,60 +820,15 @@ yarg_prefix(yang_stmt *ys) return prefix; } -/*! Split yang node identifier into prefix and identifer. - * @param[in] node-id - * @param[out] prefix Malloced string. May be NULL. - * @param[out] id Malloced identifier. - * @retval 0 OK - * @retval -1 Error - * @code - * char *prefix = NULL; - * char *id = NULL; - * if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - * goto done; - * if (prefix) - * free(prefix); - * if (id) - * free(id); - * @note caller need to free id and prefix after use - */ -int -yang_nodeid_split(char *nodeid, - char **prefix, - char **id) -{ - int retval = -1; - char *str; - - if ((str = strchr(nodeid, ':')) == NULL){ - if ((*id = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - else{ - if ((*prefix = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - (*prefix)[str-nodeid] = '\0'; - str++; - if ((*id = strdup(str)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - retval = 0; - done: - return retval; -} - -/*! Given a yang statement and a prefix, return yang module to that prefix +/*! Given a yang statement and a prefix, return yang module to that relative prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement * @param[in] prefix prefix * @retval ymod Yang module statement if found * @retval NULL not found + * @node Prefixes are relative to the module they are defined + * @see yang_find_module_by_name + * @see yang_find_module_by_namespace */ yang_stmt * yang_find_module_by_prefix(yang_stmt *ys, @@ -1081,12 +884,14 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } -/*! Given a yang statement and a namespace, return yang module +/*! Given a yang spec and a namespace, return yang module * * @param[in] yspec A yang specification * @param[in] namespace namespace * @retval ymod Yang module statement if found * @retval NULL not found + * @see yang_find_module_by_name + * @see yang_find_module_by_prefix module-specific prefix */ yang_stmt * yang_find_module_by_namespace(yang_spec *yspec, @@ -1104,6 +909,28 @@ yang_find_module_by_namespace(yang_spec *yspec, return ymod; } +/*! Given a yang spec and a module name, return yang module + * + * @param[in] yspec A yang specification + * @param[in] name Name of module + * @retval ymod Yang module statement if found + * @retval NULL not found + * @see yang_find_module_by_namespace + * @see yang_find_module_by_prefix module-specific prefix + */ +yang_stmt * +yang_find_module_by_name(yang_spec *yspec, + char *name) +{ + yang_stmt *ymod = NULL; + + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) + if ((ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE) && + strcmp(ymod->ys_argument, name)==0) + return ymod; + return NULL; +} + /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1541,7 +1368,7 @@ ys_populate_feature(clicon_handle h, if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0) continue; /* get m and f from configuration feature rules */ - if (yang_nodeid_split(xml_body(xc), &m, &f) < 0) + if (nodeid_split(xml_body(xc), &m, &f) < 0) goto done; if (m && f && (strcmp(m,"*")==0 || @@ -1558,7 +1385,8 @@ ys_populate_feature(clicon_handle h, } cv_name_set(cv, feature); cv_bool_set(cv, found); - clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found); + if (found) + clicon_debug(1, "%s %s:%s", __FUNCTION__, module, feature); ys->ys_cv = cv; ok: retval = 0; @@ -1936,7 +1764,7 @@ yang_parse_str(char *str, } /*! Parse yang spec from an open file descriptor - * @param[in] fd File descriptor containing the YANG file as ASCII characters + * @param[in] fd File descriptor containing the YANG file as ASCII characters * @param[in] name For debug, eg filename * @param[in] ysp Yang specification. Should have been created by caller using yspec_new * @retval ymod Top-level yang (sub)module @@ -2083,7 +1911,7 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; - // clicon_debug(1, "%s %s", __FUNCTION__, filename); + clicon_debug(1, "%s %s", __FUNCTION__, filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -2258,7 +2086,7 @@ yang_features(clicon_handle h, while (iys_len){ /* Note, children may be removed */ ys = yt->ys_stmt[i]; if (ys->ys_keyword == Y_IF_FEATURE){ - if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0) + if (nodeid_split(ys->ys_argument, &prefix, &feature) < 0) goto done; /* Specifically need to handle? strcmp(prefix, myprefix)) */ if (prefix == NULL) @@ -2680,7 +2508,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, char *id; char *prefix = NULL; yang_stmt *yprefix; - yang_stmt *ys; /* check absolute schema_nodeid */ if (schema_nodeid[0] != '/'){ @@ -2719,21 +2546,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } } - if (ymod == NULL){ /* Try find id from topnode without prefix XXX remove?*/ - if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - if ((ymod = ys_module(ys)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); - goto done; - } - if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) != 0){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: /* yres may not be set */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 6e08c96e..c0b1e408 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -81,7 +81,7 @@ int yang_modules_init(clicon_handle h) { - int retval = -1; + int retval = -1; yang_spec *yspec; yspec = clicon_dbspec_yang(h); @@ -203,7 +203,7 @@ yang_modules_state_get(clicon_handle h, cprintf(cb,"%s", ys->ys_argument); else cprintf(cb,""); - cprintf(cb, "implement"); + /* This follows order in rfc 7895: feature, conformance-type, submodules */ yc = NULL; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { switch(yc->ys_keyword){ @@ -211,6 +211,14 @@ yang_modules_state_get(clicon_handle h, if (yc->ys_cv && cv_bool_get(yc->ys_cv)) cprintf(cb,"%s", yc->ys_argument); break; + default: + break; + } + } + cprintf(cb, "implement"); + yc = NULL; + while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { + switch(yc->ys_keyword){ case Y_SUBMODULE: cprintf(cb,""); cprintf(cb,"%s", yc->ys_argument); diff --git a/test/all.sh b/test/all.sh index 79a0309f..1e036ab1 100755 --- a/test/all.sh +++ b/test/all.sh @@ -38,6 +38,7 @@ if [ $err -eq 0 ]; then else echo -e "\e[31mError" echo -ne "\e[0m" + exit -1 fi diff --git a/test/test_cli.sh b/test/test_cli.sh index aa1379d9..b4005433 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -59,7 +59,7 @@ expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" new "cli show configuration delete top" expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" -new "cli configure" +new "cli configure set interfaces" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" new "cli show configuration" diff --git a/test/test_compat.sh b/test/test_compat.sh new file mode 100755 index 00000000..77875410 --- /dev/null +++ b/test/test_compat.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Test of backward compatibility +# 1) Load <3.9 startup/running/extra files without namespaces - ensure it returns namespaces +# + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh +cfg=$dir/conf_startup.xml + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + example + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + 0 + init + true + + +EOF + + +run(){ + mode=$1 + expect=$2 + + dbdir=$dir/db + cat < $dbdir + + + + run + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/running_db + + cat < $dbdir + + + + startup + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/startup_db + + cat < $dir/config + + + + extra + ex:eth + + + +EOF + + new "test params: -f $cfg -s $mode -c $dir/config" + + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + + new "start backend -f $cfg -s $mode -c $dir/config" + sudo $clixon_backend -f $cfg -s $mode -c $dir/config + if [ $? -ne 0 ]; then + err + fi + fi + + new "Check $mode" + expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$expect]]>]]>$" + + if [ $BE -eq 0 ]; then + exit # BE + fi + + new "Kill backend" + # Check if premature kill + pid=`pgrep -u root -f clixon_backend` + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err "kill backend" + fi + +} # run + +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' + +rm -rf $dir diff --git a/test/test_datastore.sh b/test/test_datastore.sh index bcc7b27b..9744413d 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -50,9 +50,6 @@ EOF xml='12first-entry13second-entry23third-entryabcastring' -# Without xmlns -xmlxxx='12first-entry13second-entry23third-entryabcastring' - run(){ name=$1 mydir=$dir/$name @@ -72,7 +69,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all remove" expectfn "$datastore $conf put remove " 0 "" @@ -87,7 +84,7 @@ run(){ # expectfn "$datastore $conf put merge $xml" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all delete" expectfn "$datastore $conf put remove " 0 "" @@ -100,7 +97,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put top create" expectfn "$datastore $conf put create " 0 "" # error diff --git a/test/test_feature.sh b/test/test_feature.sh index dfb99172..20adfa3f 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -90,13 +90,13 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf enabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' "^]]>]]>$" new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' '^applicationunknown-elementyerrorUnassigned yang spec]]>]]>$' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -105,6 +105,13 @@ ret=$($clixon_netconf -qf $cfg -y $fyang<]]>]]> EOF ) +new "netconf modules-state header" +expect='^' +match=`echo "$ret" | grep -GZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + new "netconf module A" expect="exampleurn:example:clixonAimplement" match=`echo "$ret" | grep -GZo "$expect"` @@ -135,8 +142,9 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +# Note order of features in ietf-netconf yang is alphabetically: candidate, startup, validate, xpath new "netconf module ietf-netconf" -expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" +expect="ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" match=`echo "$ret" | grep -GZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/test/test_identity.sh b/test/test_identity.sh index 14d6b996..eeaa6198 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -122,44 +122,44 @@ if [ $BE -ne 0 ]; then fi new "Set crypto to aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' new "netconf validate " expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:aes]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:aes]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:foo" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:foo]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3 using xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" # XXX this is not supported #new "Set crypto to x:des3 using xmlns" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "x:des3]]>]]>" "^]]>]]>$" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'x:des3]]>]]>' "^]]>]]>$" -#new "netconf validate" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to foo:bar" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo:bar]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 6e66c7f3..63c6a270 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,22 +88,19 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " -eth0ex:eth
192.0.2.1
192.0.2.2
-loex:lo
127.0.0.1
-
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref base commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref add wrong ref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth3
10.0.4.6
]]>]]>' '^]]>]]>$' new "leafref validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' @@ -112,10 +109,10 @@ new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "leafref add correct absref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' new "leafref add correct relref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' # XXX add address @@ -123,7 +120,7 @@ new "leafref validate (ok)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "leafref delete leaf" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^' new "leafref validate (should fail)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$' diff --git a/test/test_list.sh b/test/test_list.sh index 37fb8c6c..1b9bdc2e 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -81,19 +81,19 @@ if [ $BE -ne 0 ]; then fi new "minmax: minimal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace00]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace00]]>]]>' "^]]>]]>$" new "minmax: minimal validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: maximal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace01unbounded0101unbounded01]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace01unbounded0101unbounded01]]>]]>' '^]]>]]>$' new "minmax: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: empty" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace]]>]]>' '^]]>]]>$' # NYI if false; then @@ -101,25 +101,25 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0120]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0120]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0012]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0012]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 389ae010..aac4be87 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -49,7 +49,7 @@ EOF # The groups are slightly modified from RFC8341 A.1 # The rule-list is from A.2 RULES=$(cat < + false deny deny @@ -99,7 +99,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -131,7 +131,7 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "auth set authentication config" @@ -147,33 +147,33 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 9bdee51d..b706b96f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -163,11 +163,11 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new "Set x to 0" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 0}' http://localhost/restconf/data/example:x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' @@ -176,28 +176,28 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 963218e5..b4e5bc2f 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -122,7 +122,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -158,10 +158,10 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' # Rule 1: deny-kill-session @@ -186,7 +186,7 @@ new "deny-delete-config: limited fail (restconf) ok" expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '' new2 "admin get nacm (should be null)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "deny-delete-config: admin ok (restconf)" @@ -200,14 +200,14 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" # Rule 3: permit-edit-config new "permit-edit-config: limited ok restconf" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '' new2 "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 7b060875..ced3047d 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -114,14 +114,13 @@ new "netconf get-config single quotes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'noneeth/0/0]]>]]>' "^]]>]]>$" new "Check nothing added" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" - +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" # Too many quotes, (single inside double inside single) need to fool bash cat < $tmp # new @@ -129,22 +128,22 @@ cat < $tmp # new EOF new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0ex:ethtrue]]>]]>$' new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^]]>]]>$' new "Check deleted eth/0/0 (non-presence container)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0eth1true
9.2.3.424
]]>]]>' "^]]>]]>$" # Too many quotes cat < $tmp # new @@ -152,7 +151,7 @@ cat < $tmp # new EOF new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth1true]]>]]>$' # Too many quotes cat < $tmp # new @@ -160,7 +159,7 @@ cat < $tmp # new EOF new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$' new "netconf validate missing type" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -172,13 +171,13 @@ new "netconf get empty config2" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth1ex:eth]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -187,32 +186,32 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config merge" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth2ex:ethmerge]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth2ex:ethmerge]]>]]>' "^]]>]]>$" new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth&t<>]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth&t<>]]>]]>' '^]]>]]>$' new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$' new "cli show configuration eth& - encoding tests" expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" 0 "interfaces interface eth& type t<> interfaces interface eth& enabled true" new "netconf edit CDATA" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:eth]]>]]>' "^]]>]]>$" #new "netconf get CDATA" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "eth/0/0true]]>]]>" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'eth/0/0true]]>]]>' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' "^protocolinvalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^42]]>]]>$' new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" @@ -234,7 +233,7 @@ new "copy startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth1ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth1ex:ethtrue]]>]]>$' new "netconf delete startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -243,16 +242,16 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' '^ipv4' -new "netconf rpc without namespace (iterate kludge should work)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +#new "netconf rpc without namespace (iterate kludge should work)" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf client-side rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'example]]>]]>' '^ok]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_order.sh b/test/test_order.sh index cb9eb780..4db73e9c 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -16,7 +16,6 @@ fyang=$dir/order.yang dbdir=$dir/order -new "Set up $dbdir" rm -rf $dbdir if [ ! -d $dbdir ]; then mkdir $dbdir @@ -84,30 +83,32 @@ module example{ } EOF +rm -f $dbdir/candidate_db +# alt cat < $dbdir/running_db - d - d - dbar - dbar - b - b - hej - c - c - abar - abar - hopp - a - a - cbar - cbar - bbar - bbar + d + d + dbar + dbar + b + b + hej + c + c + abar + abar + hopp + a + a + cbar + cbar + bbar + bbar EOF -new "test params: -f $cfg -y $fyang" +new "test params: -s running -f $cfg -y $fyang" if [ $BE -ne 0 ]; then new "kill old backend" @@ -123,20 +124,20 @@ if [ $BE -ne 0 ]; then fi # Check as file -new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$" +new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if CLICON_XML_SORT set to false" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "delete candidate" expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" @@ -144,33 +145,33 @@ expecteof "$clixon_netconf -qf $cfg" 0 'cb]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cb]]>]]>' "^]]>]]>$" new "add one entry to leaf-list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "add one entry to leaf-list user order after commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '0]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "verify leaf-list user order in running (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cba0]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cba0]]>]]>$' # LISTS new "add two entries to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "cbarbfoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cbarbfoo]]>]]>' "^]]>]]>$" new "add one entry to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afie]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afie]]>]]>' "^]]>]]>$" new "verify list user order (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cbarbfooafie]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_perf.sh b/test/test_perf.sh index 4d9d6dfc..eb1fe687 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -83,7 +83,7 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT new "generate 'large' config with $number list entries" -echo -n "" > $fconfig +echo -n "" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i$i" >> $fconfig done @@ -111,7 +111,7 @@ new "netconf commit large config again" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'xy]]>]]>' "^]]>]]>$" new "netconf get $req small config" time -p for (( i=0; i<$req; i++ )); do @@ -128,7 +128,7 @@ done new "netconf add $req small config" time -p for (( i=0; i<$req; i++ )); do rnd=$(( ( RANDOM % $number ) )) - echo "$rnd$rnd]]>]]>" + echo "$rnd$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add $req restconf small config" @@ -138,10 +138,10 @@ time -p for (( i=0; i<$req; i++ )); do done new "netconf get large config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^0011" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^0011' new "generate large leaf-list config" -echo -n "replace" > $fconfig +echo -n "replace" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i" >> $fconfig done @@ -158,17 +158,17 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "$rnd]]>]]>" + echo "$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'x]]>]]>' "^]]>]]>$" new "netconf commit small leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^01' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 1874a0b3..e7451d0a 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -76,7 +76,7 @@ module example{ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"state": {"op": "42"}}' +state='{"example:state": {"op": "42"}}' new "test params: -f $cfg -y $fyang" if [ $BE -ne 0 ]; then @@ -102,9 +102,6 @@ sleep $RCWAIT new "restconf tests" -new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' - new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " @@ -119,13 +116,14 @@ new2 "restconf get restconf resource. RFC 8040 3.3 (xml)" expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 ' +# Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"example:empty": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null}{"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -142,8 +140,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895" -expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} +new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" +expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"ietf-yang-library:module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} ' new "restconf options. RFC 8040 4.1" @@ -154,132 +152,133 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" +expecteq "$(curl -s -X POST -d {\"example:input\":null} http://localhost/restconf/operations/example:empty)" "" new2 "restconf empty rpc with extra args (should fail)" -expecteq "$(curl -s -X POST -d {\"input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d {\"example:input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf get empty config + state json" -expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json + module" -expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No yang node found: badmodule:state"}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No such yang module: badmodule"}}} ' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get data/ json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get state operation eth0 type json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' # Exact match new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} - ' +expectfn "curl -s -G http://localhost/restconf/data" 0 '"ietf-interfaces:interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]}' new "restconf delete interfaces" -expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" +expecteq $(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data/state" 0 "$state" +expectfn "curl -sG http://localhost/restconf/data/example:state" 0 "$state" +# XXX: gives +# new "restconf Add interfaces subtree eth/0/0 using POST" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 "" +expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" # XXX cant get this to work #expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new2 "restconf Check eth/0/0 added config" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf Check eth/0/0 added state" -expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" -expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} ' new "restconf delete eth/0/0" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-routing:output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' +#expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new2 "restconf rpc non-existing rpc without namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' @@ -291,29 +290,29 @@ new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "routing-instance-name"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) -expect="ipv42.3.4.5static" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) +expect='ipv42.3.4.5static' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) -expect="ok" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"example:input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) +expect='ok' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi # XXX cant get -H to work -#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"ietf-routing:input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' # Cant get shell macros to work, inline matching from lib.sh diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 1cf7188f..68677250 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -72,73 +72,82 @@ sleep $RCWAIT new "restconf tests" new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore intial" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' -new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}' +new "restconf GET interface subtree" +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface": \[{"name": "local0","type": "regular"}\]}' + +new "restconf GET interface subtree xml" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:cont1/interface=local0) +expect='local0regular' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 "" +# XXX should it be example:interface? new2 "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new2 "restconf POST from top" -expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf DELETE whole datastore" expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf PUT replace datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont2" 0 '{"cont2": {"name": "foo"}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2": {"name": "foo"}}' new "restconf PUT initial datastore again" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 "" new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f28a4df9..f033dfb0 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -53,50 +53,70 @@ sleep $RCWAIT new "rpc tests" # 1.First some positive tests vary media types -# +# extra complex because pattern matching on return haders +new "restconf empty rpc" +ret=$(curl -is -X POST http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "netconf empty rpc" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$' + new2 "restconf example rpc json/json default - no http media headers" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/json change y default" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "99"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "99"}} ' new2 "restconf example rpc json/json" # XXX example:input example:output -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc xml/json" -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/xml" -# 042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '042 ' new2 "restconf example rpc xml/xml" -# 0' http://localhost/restconf/operations/example:example)" '042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' http://localhost/restconf/operations/example:example)" '042 ' new "netconf example rpc" -expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' # 2. Then error cases # +new "restconf empy rpc with null input" +ret=$(curl -is -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new2 "restconf empy rpc with input x" +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "x"},"error-severity": "error"}}} ' + new2 "restconf omit mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new2 "restconf add extra" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf wrong method" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf edit-config missing mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' diff --git a/test/test_startup.sh b/test/test_startup.sh index d9ec5b07..ab0acb0f 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -41,7 +41,7 @@ run(){ dbdir=$dir/db cat < $dbdir - + run ex:eth @@ -53,7 +53,7 @@ EOF cat < $dbdir - + startup ex:eth @@ -65,7 +65,7 @@ EOF cat < $dir/config - + extra ex:eth @@ -104,8 +104,8 @@ EOF } run init '' -run none 'runex:ethtrue' -run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' -run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' +run none 'runex:ethtrue' +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' rm -rf $dir diff --git a/test/test_stream.sh b/test/test_stream.sh index 87932744..2673b832 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -65,7 +65,7 @@ EOF # using reportingEntity (rfc5277) not reporting-entity (rfc8040) cat < $fyang module example { - namespace "http://example.com/event/1.0"; + namespace "urn:example:clixon"; prefix ex; organization "Example, Inc."; contact "support at example.com"; @@ -130,35 +130,35 @@ sleep $RCWAIT new "1. Netconf RFC5277 stream testing" # 1.1 Stream discovery new "netconf event stream discovery RFC5277 Sec 3.2.5" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' new "netconf event stream discovery RFC8040 Sec 6.2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' # # 1.2 Netconf stream subscription new "netconf EXAMPLE subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf subscription with empty startTime" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with simple filter" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with filter classifier" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") #sleep 10 -#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 +#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 sleep 2 # @@ -166,11 +166,11 @@ sleep 2 new "2. Restconf RFC8040 stream testing" # 2.1 Stream discovery new "restconf event stream discovery RFC8040 Sec 6.2" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"ietf-restconf-monitoring:streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' sleep 2 new "restconf subscribe RFC8040 Sec 6.3, get location" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"ietf-restconf-monitoring:location": "https://localhost/streams/EXAMPLE"}' sleep 2 # Restconf stream subscription RFC8040 Sec 6.3 @@ -246,13 +246,12 @@ if [ $nr -lt 9 -o $nr -gt 14 ]; then err 10 "$nr" fi - # Try parallell # start background job curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" "http://localhost/streams/EXAMPLE" > /dev/null & PID=$! -new "Start subscription in parallell" +new "Start subscriptions in parallell" ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8) expect="data: ${DATE}T[0-9:.]*faultEthernet0major" diff --git a/test/test_type.sh b/test/test_type.sh index 1261d2ba..20f6f7aa 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -214,7 +214,7 @@ new "cli set transitive string error" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle 9xx" 255 "^$" new "netconf set transitive string error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '9xx]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" 'applicationbad-elementtalleerrorregexp match fail: "9xx" does not match \[a-z\]\[0-9\]\*]]>]]>$' @@ -241,7 +241,7 @@ new "cli set transitive union error int" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 55" 255 "" new "netconf set transitive union error int" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '55]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationbad-elementulleerror'55' does not match enumeration]]>]]>$" @@ -276,7 +276,7 @@ new "netconf validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a.b& c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a.b& c.d]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -298,7 +298,7 @@ expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits create" 0 "^$" #expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits \"create read\"" 0 "^$" new "netconf bits two values" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "create read]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'create read]]>]]>' "^]]>]]>$" new "cli bits validate" expectfn "$clixon_cli -1f $cfg -l o -y $fyang validate" 0 "^$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 9f4c8d4b..82fc5647 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -106,16 +106,16 @@ if [ $BE -ne 0 ]; then fi new "when: add static route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "staticr1]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'staticr1]]>]]>' "^]]>]]>$" new "when: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "when: add direct route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "directr2]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'directr2]]>]]>' "^]]>]]>$" new "when get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^directr2staticr1]]>]]>$' new "when: validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" @@ -124,19 +124,19 @@ new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet1500]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet1500]]>]]>' "^]]>]]>$" new "must: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add atm interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'atm32]]>]]>' "^]]>]]>$" new "must: atm validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn ATM MTU must be 64 .. 17966]]>]]>$" new "must: add eth interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet989]]>]]>' "^]]>]]>$" new "must: eth validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn Ethernet MTU must be 1500]]>]]>" diff --git a/test/test_yang.sh b/test/test_yang.sh index 1d953dc5..46eb6234 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -160,13 +160,13 @@ new "cli defined extension" expectfn "$clixon_cli -1f $cfg -y $fyang show version" 0 "3." new "empty values in leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "empty values in leaf-list2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^a]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^a]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -182,7 +182,7 @@ new "netconf schema resource, RFC 7895" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'ietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesimplement' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '125one]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -192,33 +192,34 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one]]>]]>$' new "netconf edit leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "hejhopp]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'hejhopp]]>]]>' "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhopp]]>]]>$' new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one' new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" + new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' '^protocolinvalid-valueerrorState data not allowed]]>]]>$' new "netconf set presence and not present" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "netconf get presence only" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" @@ -227,7 +228,7 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf anyxml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -237,28 +238,28 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>' "^]]>]]>$" new "netconf check add one 3-key" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' new "netconf add another (with same 1st key)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "121two]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '121two]]>]]>' "^]]>]]>$" new "netconf check add another" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' new "netconf replace first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "111replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111replace]]>]]>' "^]]>]]>$" new "netconf check replace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' new "netconf delete first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" new "netconf check delete" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' # clear db for next test new "netconf delete candidate" @@ -268,10 +269,10 @@ new "netconf commit empty candidate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconfig config submodule" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afoo]]>]]>' "^]]>]]>$" new "netconf submodule get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^afoo]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^afoo]]>]]>$' new "netconf submodule validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 90fc10e4..4f168fca 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -20,7 +20,7 @@ cat < $fyang1 module $APPNAME{ prefix ex; revision 2018-12-02; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf newex{ type string; } @@ -32,7 +32,7 @@ cat < $fyang2 module $APPNAME{ prefix ex; revision 2018-01-01; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf oldex{ type string; } @@ -44,7 +44,7 @@ cat < $fyang3 module other{ prefix oth; revision 2018-01-01; - namespace "urn:example:example2"; + namespace "urn:example:clixon2"; leaf other{ type string; } @@ -85,13 +85,13 @@ if [ $BE -ne 0 ]; then fi new "1. Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' if [ $BE -eq 0 ]; then exit # BE @@ -137,13 +137,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -181,13 +181,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -226,13 +226,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -270,13 +270,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -315,13 +315,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -362,13 +362,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -408,13 +408,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 19447f80..43fb2893 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -1,11 +1,15 @@ #!/bin/bash -# Yang specifics: multi-keys and empty type + APPNAME=example -# include err() and new() functions and creates $dir +# test two modules example1 and example2 with overlapping statements x. +# x is leaf in example1 and list on example2. +# Test netconf and restconf +# BTW, this is not supported in generated CLI + . ./lib.sh cfg=$dir/conf_yang.xml -fyang=$dir/example.yang +fyang1=$dir/example1.yang fyang2=$dir/example2.yang # /usr/local/share/$APPNAME/yang @@ -18,17 +22,35 @@ cat < $cfg /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME + false /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile 1 /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so + true true EOF + +cat < $fyang1 +module example1{ + yang-version 1.1; + prefix ex1; + namespace "urn:example:clixon1"; + import ietf-routing { + description "defines fib-route"; + prefix rt; + } + leaf x{ + type int32; + } +} +EOF + # For testing namespaces - -# x.y is different type. Here it is string whereas in fyang it is list. +# x.y is different type. Here it is string whereas in fyang1 it is list. # cat < $fyang2 module example2{ @@ -43,37 +65,6 @@ module example2{ } EOF -cat < $fyang -module example{ - yang-version 1.1; - prefix ex; - namespace "urn:example:clixon"; - import ietf-routing { - description "defines fib-route"; - prefix rt; - } - leaf x{ - type int32; - } - rpc client-rpc { - description "Example local client-side RPC that is processed by the - the netconf/restconf and not sent to the backend. - This is a clixon implementation detail: some rpc:s - are better processed by the client for API or perf reasons"; - input { - leaf request { - type string; - } - } - output { - leaf result{ - type string; - } - } - } -} -EOF - new "test params: -f $cfg" if [ $BE -ne 0 ]; then @@ -91,36 +82,57 @@ if [ $BE -ne 0 ]; then fi fi +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg -D $DBG" -s /bin/sh www-data & -new "netconf xmlns module ex" -expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' +new "netconf set x in example1" +expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlfn in return" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +new "netconf get config example1" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^42]]>]]>$' -new "netconf xmlns module ex2" +new "netconf set x in example2" expecteof "$clixon_netconf -qf $cfg" 0 '99]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4299]]>]]>$" +new "netconf get config example1 and example2" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^4299]]>]]>$' new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang1" 0 "]]>]]>" "^]]>]]>$" -new "netconf xmlns:ex" -expecteof "$clixon_netconf -qf $cfg" 0 '4422]]>]]>' '^]]>]]>$' +new "restconf set x in example1" +expecteq "$(curl -s -X POST -d '{"example1:x":42}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4422]]>]]>$" +new2 "restconf get config example1" +expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} + ' -new "netconf xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg" 0 '9999]]>]]>' '^]]>]]>$' +new "restconf set x in example2" +expecteq "$(curl -s -X POST -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^44229999]]>]]>$" +# XXX GET ../example1:x is translated to select=/x which gets both example1&2 +#new "restconf get config example1" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} +# ' -# rpc +# XXX GET ../example2:x is translated to select=/x which gets both example1&2 +#new "restconf get config example2" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" '{"example2:x": {"y":42}} +# ' + +new "restconf get config example1 and example2" +ret=$(curl -s -X GET http://localhost/restconf/data) +expect='"example1:x": 42,"example2:x": {"y": 99}' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" if [ $BE -eq 0 ]; then exit # BE diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 162c3c0b..99d5b596 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -353,14 +353,19 @@ module clixon-config { Only works for Yang specified XML. If not set, all lists accessed via linear search."; } - leaf CLICON_XML_NS_ITERATE { + leaf CLICON_XML_NS_STRICT { type boolean; default true; description - "If set, iterate through modules to find the matching datanode + "If not set, make non-strict laze namespace checks, by iterating + through modules to find the matching datanode or rpc if no xmlns attribute specifies namespace. - This is loose semantics of finding namespaces. - And it is wrong, but is the way Clixon originally was written."; + This is lazy semantics of finding namespaces, which means you + do not need to explicitly give the namespace if the symbol exists + in some loaded module. + Example: is enough, instead of + But it is wrong, but is the way Clixon originally was written. + Strict semantics is the default."; } leaf CLICON_USE_STARTUP_CONFIG { type int32; diff --git a/yang/ietf-netconf-notification@2008-07-01.yang b/yang/ietf-netconf-notification@2008-07-01.yang index 978cdd4b..1db3ee3e 100644 --- a/yang/ietf-netconf-notification@2008-07-01.yang +++ b/yang/ietf-netconf-notification@2008-07-01.yang @@ -1,5 +1,6 @@ module ietf-netconf-notification { - namespace "urn:ietf:params:xml:ns:netconf:notification:1:0"; + /* namespace "urn:ietf:params:xml:ns:netconf:notification:1.0";*/ + namespace "urn:ietf:params:xml:ns:netmod:notification"; prefix "rcmon"; import ietf-yang-types { prefix yang; }