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: */
+#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: