diff --git a/CHANGELOG.md b/CHANGELOG.md
index 160f62540..0035a93d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,6 @@
* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized)
### Major New features
-
* NACM extension (RFC8341)
* NACM module support (RFC8341 A1+A2)
* Recovery user "_nacm_recovery" added.
@@ -20,6 +19,8 @@
* Support of submodule, include and belongs-to.
* Openconfig yang specs parsed: https://github.com/openconfig/public
* Improved "unknown" handling
+ * More precise Yang validation and better error messages
+ * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed
* Yang load file configure options changed
* `CLICON_YANG_DIR` is changed from a single directory to a path of directories
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
@@ -27,18 +28,20 @@
* 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.
+* 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
@@ -49,6 +52,8 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes
+* Added example_rpc RPC to example backend
+* Renamed xml_namespace[_set]() to xml_prefix[_set]()
* Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3)
* Removed return value ymodp from yang parse functions (eg yang_parse()).
@@ -60,8 +65,10 @@
* config", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0)
goto done;
goto ok;
}
else{
- /* yang spec may be set to anyxml by ingress yang check,...*/
+ /* yang spec may be set to anyxmly by ingress yang check,...*/
if (xml_spec(xc) != NULL)
xml_spec_set(xc, NULL);
/* Populate XML with Yang spec (why not do this in parser?)
@@ -530,7 +530,7 @@ from_client_lock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@@ -589,7 +589,7 @@ from_client_unlock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@@ -651,7 +651,7 @@ from_client_kill_session(clicon_handle h,
if ((x = xml_find(xe, "session-id")) == NULL ||
(str = xml_find_value(x, "body")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0)
goto done;
goto ok;
}
@@ -709,7 +709,7 @@ from_client_copy_config(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((source = netconf_db_find(xe, "source")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done;
goto ok;
}
@@ -724,7 +724,7 @@ from_client_copy_config(clicon_handle h,
goto ok;
}
if ((target = netconf_db_find(xe, "target")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@@ -777,7 +777,7 @@ from_client_delete_config(clicon_handle h,
if ((target = netconf_db_find(xe, "target")) == NULL||
strcmp(target, "running")==0){
- if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@@ -856,7 +856,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//stopTime")) != NULL){
if ((stoptime = xml_find_value(x, "body")) != NULL &&
str2time(stoptime, &stop) < 0){
- if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
+ if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
@@ -864,7 +864,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//startTime")) != NULL){
if ((starttime = xml_find_value(x, "body")) != NULL &&
str2time(starttime, &start) < 0){
- if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
+ if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
@@ -925,7 +925,7 @@ from_client_debug(clicon_handle h,
char *valstr;
if ((valstr = xml_find_body(xe, "level")) == NULL){
- if (netconf_missing_element(cbret, "application", "level", NULL) < 0)
+ if (netconf_missing_element(cbret, "application", "level", NULL) < 0)
goto done;
goto ok;
}
@@ -993,13 +993,10 @@ from_client_msg(clicon_handle h,
/* Populate incoming XML tree with yang */
if (xml_spec_populate_rpc(h, x, yspec) < 0)
goto done;
- if ((ret = xml_yang_validate_rpc(x)) < 0)
+ if ((ret = xml_yang_validate_rpc(x, cbret)) < 0)
goto done;
- if (ret == 0){
- if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
- goto done;
+ if (ret == 0)
goto reply;
- }
xe = NULL;
username = xml_find_value(x, "username");
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
@@ -1059,7 +1056,7 @@ from_client_msg(clicon_handle h,
}
else if (strcmp(rpc, "validate") == 0){
if ((db = netconf_db_find(xe, "source")) == NULL){
- if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
+ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done;
goto reply;
}
diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c
index 3aa2f82d4..88ea9e99a 100644
--- a/apps/backend/backend_commit.c
+++ b/apps/backend/backend_commit.c
@@ -81,68 +81,86 @@
* are if code comes via XML/NETCONF.
* @param[in] yspec Yang spec
* @param[in] td Transaction data
+ * @param[out] cbret Cligen buffer containing netconf error (if retval == 0)
+ * @retval -1 Error
+ * @retval 0 Validation failed (with cbret set)
+ * @retval 1 Validation OK
*/
static int
generic_validate(yang_spec *yspec,
- transaction_data_t *td)
+ transaction_data_t *td,
+ cbuf *cbret)
{
int retval = -1;
cxobj *x1;
cxobj *x2;
yang_stmt *ys;
int i;
+ int ret;
/* All entries */
- if (xml_apply(td->td_target, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
+ if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0)
goto done;
-
+ if (ret == 0)
+ goto fail;
/* changed entries */
for (i=0; itd_clen; i++){
x1 = td->td_scvec[i]; /* source changed */
x2 = td->td_tcvec[i]; /* target changed */
- if (xml_yang_validate_add(x2, NULL) < 0)
+ /* Should this be recursive? */
+ if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
}
/* deleted entries */
for (i=0; itd_dlen; i++){
x1 = td->td_dvec[i];
ys = xml_spec(x1);
if (ys && yang_mandatory(ys)){
- clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
- xml_name(x1));
- goto done;
+ if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Removed mandatory variable") < 0)
+ goto done;
+ goto fail;
}
}
/* added entries */
for (i=0; itd_alen; i++){
x2 = td->td_avec[i];
- if (xml_apply0(x2, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_add, NULL) < 0)
+ if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
}
- retval = 0;
+ // ok:
+ retval = 1;
done:
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Common code of candidate_validate and candidate_commit
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
- * @retval 0 OK
- * @retval -1 Fatal error or validation fail
- * @note Need to differentiate between error and validation fail
+ * @retval -1 Error - or validation failed (but cbret not set)
+ * @retval 0 Validation failed (with cbret set)
+ * @retval 1 Validation OK
+ * @note Need to differentiate between error and validation fail
+ * (only done for generic_validate)
*/
static int
validate_common(clicon_handle h,
char *candidate,
- transaction_data_t *td)
+ transaction_data_t *td,
+ cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int i;
cxobj *xn;
-
+ int ret;
+
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@@ -193,9 +211,12 @@ validate_common(clicon_handle h,
if (plugin_transaction_begin(h, td) < 0)
goto done;
- /* 5. Make generic validation on all new or changed data. */
- if (generic_validate(yspec, td) < 0)
+ /* 5. Make generic validation on all new or changed data.
+ Note this is only call that uses 3-values */
+ if ((ret = generic_validate(yspec, td, cbret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
/* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0)
@@ -204,9 +225,12 @@ validate_common(clicon_handle h,
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
- retval = 0;
+ retval = 1;
done:
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Do a diff between candidate and running, then start a commit transaction
@@ -216,24 +240,30 @@ validate_common(clicon_handle h,
* do something more drastic?
* @param[in] h Clicon handle
* @param[in] candidate A candidate database, not necessarily "candidate"
- * @retval 0 OK
- * @retval -1 Fatal error or validation fail
+ * @retval -1 Error - or validation failed (but cbret not set)
+ * @retval 0 Validation failed (with cbret set)
+ * @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
+ * (only done for validate_common)
*/
int
candidate_commit(clicon_handle h,
- char *candidate)
+ char *candidate,
+ cbuf *cbret)
{
- int retval = -1;
+ int retval = -1;
transaction_data_t *td = NULL;
+ int ret;
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
- /* Common steps (with validate) */
- if (validate_common(h, candidate, td) < 0)
+ /* Common steps (with validate). Note this is only call that uses 3-values */
+ if ((ret = validate_common(h, candidate, td, cbret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
/* 7. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
@@ -252,21 +282,23 @@ candidate_commit(clicon_handle h,
/* 9. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
-
/* 8. Copy running back to candidate in case end functions updated running */
if (xmldb_copy(h, "running", candidate) < 0){
/* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
- retval = 0;
+ retval = 1;
done:
- /* In case of failure, call plugin transaction termination callbacks */
- if (retval < 0 && td)
+ /* In case of failure (or error), call plugin transaction termination callbacks */
+ if (retval < 1 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Commit changes from candidate to running
@@ -283,6 +315,7 @@ from_client_commit(clicon_handle h,
int retval = -1;
int piddb;
cbuf *cbx = NULL; /* Assist cbuf */
+ int ret;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "running");
@@ -296,9 +329,10 @@ from_client_commit(clicon_handle h,
goto done;
goto ok;
}
- if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */
+ if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */
clicon_debug(1, "Commit candidate failed");
- if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0)
+ if (ret < 0)
+ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
@@ -367,6 +401,7 @@ from_client_validate(clicon_handle h,
{
int retval = -1;
transaction_data_t *td = NULL;
+ int ret;
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
if (netconf_invalid_value(cbret, "protocol", "No such database")< 0)
@@ -379,11 +414,12 @@ from_client_validate(clicon_handle h,
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with commit) */
- if (validate_common(h, db, td) < 0){
+ if ((ret = validate_common(h, db, td, cbret)) < 1){
clicon_debug(1, "Validate %s failed", db);
- /* XXX: candidate_validate should have proper error handling */
- if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
- goto done;
+ if (ret < 0){
+ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
+ goto done;
+ }
goto ok;
}
/* Optionally write (potentially modified) tree back to candidate */
diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h
index 6be05858a..45b0f9224 100644
--- a/apps/backend/backend_commit.h
+++ b/apps/backend/backend_commit.h
@@ -43,6 +43,6 @@
int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
-int candidate_commit(clicon_handle h, char *db);
+int candidate_commit(clicon_handle h, char *db, cbuf *cbret);
#endif /* _BACKEND_COMMIT_H_ */
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index ec4a99d73..8e433d855 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -386,6 +386,7 @@ startup_mode_running(clicon_handle h,
char *extraxml_file)
{
int retval = -1;
+ cbuf *cbret = NULL;
/* Stash original running to candidate for later commit */
if (xmldb_copy(h, "running", "candidate") < 0)
@@ -405,15 +406,20 @@ startup_mode_running(clicon_handle h,
/* 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") < 0){
+ 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.", __FUNCTION__);
+ 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;
@@ -424,6 +430,8 @@ startup_mode_running(clicon_handle h,
goto done;
retval = 0;
done:
+ if (cbret)
+ cbuf_free(cbret);
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
@@ -455,6 +463,7 @@ startup_mode_startup(clicon_handle h,
char *extraxml_file)
{
int retval = -1;
+ cbuf *cbret = NULL;
/* Stash original running to backup */
if (xmldb_copy(h, "running", "backup") < 0)
@@ -478,13 +487,19 @@ startup_mode_startup(clicon_handle h,
/* 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") < 0){ /* diff */
+ 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.", __FUNCTION__);
+ 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;
@@ -494,6 +509,8 @@ startup_mode_startup(clicon_handle h,
goto done;
retval = 0;
done:
+ if (cbret)
+ cbuf_free(cbret);
if (xmldb_delete(h, "backup") < 0)
goto done;
if (xmldb_delete(h, "tmp") < 0)
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index 55ee628b6..cc4c97105 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -83,15 +83,17 @@ static int
process_incoming_packet(clicon_handle h,
cbuf *cb)
{
- char *str;
- char *str0;
- cxobj *xreq = NULL; /* Request (in) */
- int isrpc = 0; /* either hello or rpc */
- cbuf *cbret = NULL;
- cxobj *xret = NULL; /* Return (out) */
- cxobj *xrpc;
- cxobj *xc;
+ int retval = -1;
+ char *str;
+ char *str0;
+ cxobj *xreq = NULL; /* Request (in) */
+ int isrpc = 0; /* either hello or rpc */
+ cbuf *cbret = NULL;
+ cxobj *xret = NULL; /* Return (out) */
+ cxobj *xrpc;
+ cxobj *xc;
yang_spec *yspec;
+ int ret;
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
@@ -115,15 +117,12 @@ process_incoming_packet(clicon_handle h,
}
free(str0);
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
- int ret;
isrpc++;
- if ((ret = xml_yang_validate_rpc(xrpc)) < 0)
+ if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0)
goto done;
if (ret == 0){
- if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
- goto done;
netconf_output(1, cbret, "rpc-error");
- goto done;
+ goto ok;
}
}
else
@@ -168,6 +167,8 @@ process_incoming_packet(clicon_handle h,
}
}
}
+ ok:
+ retval = 0;
done:
if (xreq)
xml_free(xreq);
@@ -175,7 +176,7 @@ process_incoming_packet(clicon_handle h,
xml_free(xret);
if (cbret)
cbuf_free(cbret);
- return 0;
+ return retval;
}
/*! Get netconf message: detect end-of-msg
@@ -229,7 +230,7 @@ netconf_input_cb(int s,
/* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (process_incoming_packet(h, cb) < 0)
- goto done;
+ ; //goto done; // ignore errors
if (cc_closed)
break;
cbuf_reset(cb);
diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c
index f53b8675a..640011373 100644
--- a/apps/netconf/netconf_rpc.c
+++ b/apps/netconf/netconf_rpc.c
@@ -56,6 +56,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -876,12 +877,16 @@ netconf_application_rpc(clicon_handle h,
cbuf *cb = NULL;
cbuf *cbret = NULL;
int ret;
-
+
/* First check system / netconf RPC:s */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
+ if ((cbret = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, 0, "cbuf_new");
+ goto done;
+ }
/* Find yang rpc statement, return yang rpc statement if found
Check application RPC */
if ((yspec = clicon_dbspec_yang(h)) == NULL){
@@ -917,15 +922,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
- if (xml_apply(xn, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
+ if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0)
goto done;
- if (xml_yang_validate_add(xn, NULL) < 0)
+ if (ret == 0){
+ netconf_output(1, cbret, "rpc-error");
+ goto ok;
+ }
+ if ((ret = xml_yang_validate_add(xn, cbret)) < 0)
goto done;
- }
- if ((cbret = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, 0, "cbuf_new");
- goto done;
+ if (ret == 0){
+ netconf_output(1, cbret, "rpc-error");
+ goto ok;
+ }
}
/* Look for local (client-side) netconf plugins. */
if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0)
@@ -943,11 +951,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
- if (xml_apply(xoutput, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
+ if ((ret = xml_yang_validate_all_top(xoutput, cbret)) < 0)
goto done;
- if (xml_yang_validate_add(xoutput, NULL) < 0)
+ if (ret == 0){
+ clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
+ goto ok;
+ }
+ if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0)
goto done;
+ if (ret == 0){
+ clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
+ goto ok;
+ }
}
retval = 1; /* handled by callback */
goto done;
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index 75c066991..be214fbb7 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -71,11 +71,12 @@ static const map_str2int netconf_restconf_map[] = {
{"missing-attribute", 400},
{"bad-attribute", 400},
{"unknown-attribute", 400},
+ {"missing-element", 400},
{"bad-element", 400},
{"unknown-element", 400},
{"unknown-namespace", 400},
- {"access-denied", 401},
- {"access-denied", 403},
+ {"access-denied", 401}, /* or 403 */
+ {"access-denied", 403},
{"lock-denied", 409},
{"resource-denied", 409},
{"rollback-failed", 500},
@@ -436,7 +437,8 @@ api_return_err(clicon_handle h,
goto ok;
}
tagstr = xml_body(xtag);
- code = restconf_err2code(tagstr);
+ if ((code = restconf_err2code(tagstr)) < 0)
+ code = 500; /* internal server error */
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (xml_name_set(xerr, "error") < 0)
@@ -448,6 +450,7 @@ api_return_err(clicon_handle h,
else
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
+ FCGX_SetExitStatus(code, r->out); /* Created */
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n",
use_xml?"xml":"json");
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 42d4d331e..b8beb0686 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -190,7 +190,7 @@ api_data_get2(clicon_handle h,
yang_spec *yspec;
cxobj *xret = NULL;
cxobj *xerr = NULL; /* malloced */
- cxobj *xe;
+ cxobj *xe = NULL;
cxobj **xvec = NULL;
size_t xlen;
int i;
@@ -206,8 +206,9 @@ api_data_get2(clicon_handle h,
if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
- if (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
path = cbuf_get(cbpath);
@@ -215,8 +216,9 @@ 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
/* We get return via netconf which is complete tree from root
@@ -434,16 +436,18 @@ api_data_post(clicon_handle h,
if (xml_parse_string(data, NULL, &xdata) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
- if (api_return_err(h, r, xerr, pretty, use_xml) < 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
/* 4.4.1: The message-body MUST contain exactly one instance of the
@@ -452,8 +456,9 @@ 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
x = xml_child_i(xdata,0);
@@ -662,16 +667,18 @@ api_data_put(clicon_handle h,
if (xml_parse_string(data, NULL, &xdata) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
- if (api_return_err(h, r, xerr, pretty, use_xml) < 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
/* The message-body MUST contain exactly one instance of the
@@ -680,8 +687,9 @@ api_data_put(clicon_handle h,
if (xml_child_nr(xdata) != 1){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
- if (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
x = xml_child_i(xdata,0);
@@ -705,8 +713,9 @@ 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
/* If list or leaf-list, api-path keys must match data keys */
@@ -714,8 +723,9 @@ 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 (api_return_err(h, r, xerr, pretty, use_xml) < 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;
}
}
@@ -866,7 +876,7 @@ api_data_delete(clicon_handle h,
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
- if (xml_value_set(xa, xml_operation2str(op)) < 0)
+ if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
@@ -1054,7 +1064,6 @@ api_operations_post(clicon_handle h,
cxobj *xdata = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
- cxobj *xer; /* non-malloced error */
cbuf *cbx = NULL;
cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
@@ -1076,13 +1085,18 @@ api_operations_post(clicon_handle h,
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
+ if ((cbret = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, 0, "cbuf_new");
+ goto done;
+ }
for (i=0; i h ?*/
if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
- if (xml_apply(xbot, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
+ if ((ret = xml_yang_validate_all(xbot, cbret)) < 0)
goto done;
- if (xml_yang_validate_add(xbot, NULL) < 0){
- if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
+ 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 (api_return_err(h, r, xerr, pretty, use_xml) < 0)
+ 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;
}
}
}
- if ((cbret = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, 0, "cbuf_new");
- goto done;
- }
+ ret = 0;
xe = NULL;
while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) {
/* Look for local (client-side) restconf plugins. */
@@ -1208,8 +1238,8 @@ api_operations_post(clicon_handle h,
if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0)
goto done;
/* Local error: return it and quit */
- if ((xer = xpath_first(xret, "//rpc-error")) != NULL){
- if (api_return_err(h, r, xer, pretty, use_xml) < 0)
+ 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;
}
@@ -1219,8 +1249,8 @@ api_operations_post(clicon_handle h,
if (ret == 0){ /* Send to backend */
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
goto done;
- if ((xer = xpath_first(xret, "//rpc-error")) != NULL){
- if (api_return_err(h, r, xer, pretty, use_xml) < 0)
+ 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;
}
@@ -1240,18 +1270,31 @@ api_operations_post(clicon_handle h,
goto done;
if ((xoutput=xpath_first(xret, "/")) != NULL){
xml_name_set(xoutput, "output");
-#if 0
- clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx));
-#endif
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 (xml_apply(xoutput, CX_ELMNT,
- (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
+ if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0)
goto done;
- if (xml_yang_validate_add(xoutput, NULL) < 0)
+ 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 */
FCGX_SetExitStatus(200, r->out); /* OK */
diff --git a/example/example_backend.c b/example/example_backend.c
index 3a420a0e5..35fe20d4f 100644
--- a/example/example_backend.c
+++ b/example/example_backend.c
@@ -132,6 +132,7 @@ fib_route(clicon_handle h, /* Clicon handle */
cprintf(cbret, ""
"ipv4"
"2.3.4.5"
+ "static"
"");
return 0;
}
@@ -157,16 +158,41 @@ route_count(clicon_handle h,
* in [RFC6241].
*/
static int
-empty(clicon_handle h, /* Clicon handle */
- cxobj *xe, /* Request: */
- cbuf *cbret, /* Reply eg ... */
- void *arg, /* client_entry */
- void *regarg) /* Argument given at register */
+empty_rpc(clicon_handle h, /* Clicon handle */
+ cxobj *xe, /* Request: */
+ cbuf *cbret, /* Reply eg ... */
+ void *arg, /* client_entry */
+ void *regarg) /* Argument given at register */
{
cprintf(cbret, "");
return 0;
}
+/*! More elaborate example RPC for testing
+ * The RPC returns the incoming parameters
+ */
+static int
+example_rpc(clicon_handle h, /* Clicon handle */
+ cxobj *xe, /* Request: */
+ cbuf *cbret, /* Reply eg ... */
+ void *arg, /* client_entry */
+ void *regarg) /* Argument given at register */
+{
+ int retval = -1;
+ cxobj *x = NULL;
+
+ cprintf(cbret, "");
+ while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
+ if (clicon_xml2cbuf(cbret, x, 0, 0) < 0)
+ goto done;
+ }
+ cprintf(cbret, "");
+ retval = 0;
+ done:
+ return retval;
+}
+
+
/*! Called to get state data from plugin
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
@@ -315,22 +341,31 @@ clixon_plugin_init(clicon_handle h)
if (example_stream_timer_setup(h) < 0)
goto done;
- /* Register callback for routing rpc calls */
+ /* Register callback for routing rpc calls
+ */
+
if (rpc_callback_register(h, fib_route,
NULL,
"fib-route"/* Xml tag when callback is made */
) < 0)
goto done;
+ /* From ietf-routing.yang */
if (rpc_callback_register(h, route_count,
NULL,
"route-count"/* Xml tag when callback is made */
) < 0)
goto done;
- if (rpc_callback_register(h, empty,
+ /* From example.yang (clicon) */
+ if (rpc_callback_register(h, empty_rpc,
NULL,
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
+ if (rpc_callback_register(h, example_rpc,
+ NULL,
+ "example"/* Xml tag when callback is made */
+ ) < 0)
+ goto done;
/* Return plugin API */
return &api;
diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h
index cdf570e9e..b22e697b9 100644
--- a/lib/clixon/clixon_netconf_lib.h
+++ b/lib/clixon/clixon_netconf_lib.h
@@ -46,9 +46,9 @@ int netconf_too_big(cbuf *cb, char *type, char *message);
int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message);
-int netconf_missing_element(cbuf *cb, char *type, char *info, char *message);
-int netconf_bad_element(cbuf *cb, char *type, char *info, char *message);
-int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message);
+int netconf_missing_element(cbuf *cb, char *type, char *element, char *message);
+int netconf_bad_element(cbuf *cb, char *type, char *info, char *element);
+int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message);
int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message);
int netconf_access_denied(cbuf *cb, char *type, char *message);
int netconf_access_denied_xml(cxobj **xret, char *type, char *message);
diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h
index 803e5948a..68f332aba 100644
--- a/lib/clixon/clixon_xml.h
+++ b/lib/clixon/clixon_xml.h
@@ -99,8 +99,8 @@ extern int _CLICON_XML_NS_ITERATE;
char *xml_type2str(enum cxobj_type type);
char *xml_name(cxobj *xn);
int xml_name_set(cxobj *xn, char *name);
-char *xml_namespace(cxobj *xn);
-int xml_namespace_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);
cxobj *xml_parent(cxobj *xn);
int xml_parent_set(cxobj *xn, cxobj *parent);
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index abd1fe50c..dba82884c 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -43,9 +43,10 @@
*/
int xml2txt(FILE *f, cxobj *x, int level);
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
-int xml_yang_validate_rpc(cxobj *xrpc);
-int xml_yang_validate_add(cxobj *xt, void *arg);
-int xml_yang_validate_all(cxobj *xt, void *arg);
+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 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,
diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c
index 00778c303..06aa71c95 100644
--- a/lib/src/clixon_json.c
+++ b/lib/src/clixon_json.c
@@ -315,7 +315,7 @@ xml2json1_cbuf(cbuf *cb,
* Harder if x has a prefix, then that should also be translated to associated
* module name
*/
- prefix = xml_namespace(x);
+ prefix = xml_prefix(x);
if (xml2ns(x, prefix, &namespace) < 0)
goto done;
if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index ccb88275d..6c284fc6c 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -80,8 +80,8 @@ netconf_in_use(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "in-use"
"%s"
+ "in-use"
"error",
type) <0)
goto err;
@@ -119,8 +119,8 @@ netconf_invalid_value(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "invalid-value"
"%s"
+ "invalid-value"
"error",
type) <0)
goto err;
@@ -159,8 +159,8 @@ netconf_too_big(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "too-big"
"%s"
+ "too-big"
"error",
type) <0)
goto err;
@@ -200,8 +200,8 @@ netconf_missing_attribute(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "missing-attribute"
"%s"
+ "missing-attribute"
"%s"
"error",
type, info) <0)
@@ -241,8 +241,8 @@ netconf_bad_attribute(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "bad-attribute"
"%s"
+ "bad-attribute"
"%s"
"error",
type, info) <0)
@@ -283,8 +283,8 @@ netconf_unknown_attribute(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "unknown-attribute"
"%s"
+ "unknown-attribute"
"%s"
"error",
type, info) <0)
@@ -318,18 +318,18 @@ netconf_unknown_attribute(cbuf *cb,
int
netconf_missing_element(cbuf *cb,
char *type,
- char *info,
+ char *element,
char *message)
{
int retval = -1;
char *encstr = NULL;
if (cprintf(cb, ""
- "missing-element"
"%s"
- "%s"
+ "missing-element"
+ "%s"
"error",
- type, info) <0)
+ type, element) <0)
goto err;
if (message){
if (xml_chardata_encode(&encstr, "%s", message) < 0)
@@ -355,24 +355,24 @@ netconf_missing_element(cbuf *cb,
* pattern mismatch.
* @param[out] cb CLIgen buf. Error XML is written in this buffer
* @param[in] type Error type: "application" or "protocol"
- * @param[in] info bad-element xml
+ * @param[in] elemnt Bad element name
* @param[in] message Error message
*/
int
netconf_bad_element(cbuf *cb,
char *type,
- char *info,
+ char *element,
char *message)
{
int retval = -1;
char *encstr = NULL;
if (cprintf(cb, ""
- "bad-element"
"%s"
- "%s"
+ "bad-element"
+ "%s"
"error",
- type, info) <0)
+ type, element) <0)
goto err;
if (message){
if (xml_chardata_encode(&encstr, "%s", message) < 0)
@@ -397,24 +397,24 @@ netconf_bad_element(cbuf *cb,
* An unexpected element is present.
* @param[out] cb CLIgen buf. Error XML is written in this buffer
* @param[in] type Error type: "application" or "protocol"
- * @param[in] info bad-element xml
+ * @param[in] element Bad element name
* @param[in] message Error message
*/
int
netconf_unknown_element(cbuf *cb,
char *type,
- char *info,
+ char *element,
char *message)
{
int retval = -1;
char *encstr = NULL;
if (cprintf(cb, ""
- "unknown-element"
"%s"
- "%s"
+ "unknown-element"
+ "%s"
"error",
- type, info) <0)
+ type, element) <0)
goto err;
if (message){
if (xml_chardata_encode(&encstr, "%s", message) < 0)
@@ -452,8 +452,8 @@ netconf_unknown_namespace(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "unknown-namespace"
"%s"
+ "unknown-namespace"
"%s"
"error",
type, info) <0)
@@ -478,7 +478,8 @@ netconf_unknown_namespace(cbuf *cb,
/*! Create Netconf access-denied error XML tree according to RFC 6241 App A
*
- * An expected element is missing.
+ * Access to the requested protocol operation or data model is denied because
+ * authorization failed.
* @param[out] cb CLIgen buf. Error XML is written in this buffer
* @param[in] type Error type: "application" or "protocol"
* @param[in] message Error message
@@ -492,8 +493,8 @@ netconf_access_denied(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "access-denied"
"%s"
+ "access-denied"
"error",
type) <0)
goto err;
@@ -517,7 +518,8 @@ netconf_access_denied(cbuf *cb,
/*! Create Netconf access-denied error XML tree according to RFC 6241 App A
*
- * An expected element is missing.
+ * Access to the requested protocol operation or data model is denied because
+ * authorization failed.
* @param[out] xret Error XML tree
* @param[in] type Error type: "application" or "protocol"
* @param[in] message Error message
@@ -538,8 +540,8 @@ netconf_access_denied_xml(cxobj **xret,
goto done;
if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL)
goto done;
- if (xml_parse_va(&xerr, NULL, "access-denied"
- "%s"
+ if (xml_parse_va(&xerr, NULL, "%s"
+ "access-denied"
"error", type) < 0)
goto done;
if (message && xml_parse_va(&xerr, NULL, "%s",
@@ -567,8 +569,8 @@ netconf_lock_denied(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "lock-denied"
"protocol"
+ "lock-denied"
"%s"
"error",
info) <0)
@@ -593,7 +595,7 @@ netconf_lock_denied(cbuf *cb,
/*! Create Netconf resource-denied error XML tree according to RFC 6241 App A
*
- * An expected element is missing.
+ * Request could not be completed because of insufficient resources.
* @param[out] cb CLIgen buf. Error XML is written in this buffer
* @param[in] type Error type: "transport, "rpc", "application", "protocol"
* @param[in] message Error message
@@ -607,8 +609,8 @@ netconf_resource_denied(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "resource-denied"
"%s"
+ "resource-denied"
"error",
type) <0)
goto err;
@@ -647,8 +649,8 @@ netconf_rollback_failed(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "rollback-failed"
"%s"
+ "rollback-failed"
"error",
type) <0)
goto err;
@@ -686,8 +688,8 @@ netconf_data_exists(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "data-exists"
"application"
+ "data-exists"
"error") <0)
goto err;
if (message){
@@ -724,8 +726,8 @@ netconf_data_missing(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "data-missing"
"application"
+ "data-missing"
"error") <0)
goto err;
if (message){
@@ -763,8 +765,8 @@ netconf_operation_not_supported(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "operation-not-supported"
"%s"
+ "operation-not-supported"
"error",
type) <0)
goto err;
@@ -803,8 +805,8 @@ netconf_operation_failed(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "operation-failed"
"%s"
+ "operation-failed"
"error",
type) <0)
goto err;
@@ -850,8 +852,8 @@ netconf_operation_failed_xml(cxobj **xret,
goto done;
if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL)
goto done;
- if (xml_parse_va(&xerr, NULL, "operation-failed"
- "%s"
+ if (xml_parse_va(&xerr, NULL, "%s"
+ "operation-failed"
"error", type) < 0)
goto done;
if (message && xml_parse_va(&xerr, NULL, "%s",
@@ -879,8 +881,8 @@ netconf_malformed_message(cbuf *cb,
char *encstr = NULL;
if (cprintf(cb, ""
- "malformed-message"
"rpc"
+ "malformed-message"
"error") <0)
goto err;
if (message){
@@ -925,8 +927,8 @@ netconf_malformed_message_xml(cxobj **xret,
goto done;
if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL)
goto done;
- if (xml_parse_va(&xerr, NULL, "malformed-message"
- "rpc"
+ if (xml_parse_va(&xerr, NULL, "rpc"
+ "malformed-message"
"error") < 0)
goto done;
if (message && xml_parse_va(&xerr, NULL, "%s",
@@ -990,7 +992,6 @@ netconf_module_load(clicon_handle h)
clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
goto done;
}
-
/* Enable features (hardcoded here) */
if (xml_parse_string("ietf-netconf:candidate", yspec, &xc) < 0)
goto done;
diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c
index 873d3aafa..45c3948bb 100644
--- a/lib/src/clixon_options.c
+++ b/lib/src/clixon_options.c
@@ -136,6 +136,8 @@ parse_configfile(clicon_handle h,
char *name;
char *body;
clicon_hash_t *copt = clicon_options(h);
+ cbuf *cbret = NULL;
+ int ret;
if (filename == NULL || !strlen(filename)){
clicon_err(OE_UNIX, 0, "Not specified");
@@ -167,8 +169,16 @@ parse_configfile(clicon_handle h,
}
if (xml_apply0(xc, CX_ELMNT, xml_default, yspec) < 0)
goto done;
- if (xml_apply0(xc, CX_ELMNT, xml_yang_validate_add, NULL) < 0)
+ if ((cbret = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
goto done;
+ }
+ if ((ret = xml_yang_validate_add(xc, cbret)) < 0)
+ goto done;
+ if (ret == 0){
+ clicon_err(OE_CFG, 0, "Config file validation: %s", cbuf_get(cbret));
+ goto done;
+ }
while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) {
name = xml_name(x);
body = xml_body(x);
diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c
index 0cf421d2f..aa3ac4ac0 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -444,7 +444,7 @@ clicon_rpc_delete_config(clicon_handle h,
char *username;
username = clicon_username_get(h);
- if ((msg = clicon_msg_encode("<%s/>",
+ if ((msg = clicon_msg_encode("<%s/>none",
username?username:"", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 24bab41be..59292ba93 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -196,10 +196,9 @@ xml_name_set(cxobj *xn,
/*! Get namespace of xnode
* @param[in] xn xml node
* @retval namespace of xml node
- * XXX change to xml_localname
*/
char*
-xml_namespace(cxobj *xn)
+xml_prefix(cxobj *xn)
{
return xn->x_prefix;
}
@@ -209,11 +208,10 @@ xml_namespace(cxobj *xn)
* @param[in] localname new namespace, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
- * XXX change to xml_localname_set
*/
int
-xml_namespace_set(cxobj *xn,
- char *localname)
+xml_prefix_set(cxobj *xn,
+ char *localname)
{
if (xn->x_prefix){
free(xn->x_prefix);
@@ -288,7 +286,7 @@ xmlns_check(cxobj *xn,
char *xns;
while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL)
- if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 &&
+ if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 &&
strcmp(xml_name(x), nsn) == 0)
return xml_value(x);
return NULL;
@@ -311,7 +309,7 @@ xml_localname_check(cxobj *xn,
yang_stmt *ys = xml_spec(xn);
/* No namespace name - comply */
- if ((nsn = xml_namespace(xn)) == NULL)
+ if ((nsn = xml_prefix(xn)) == NULL)
return 0;
/* Check if NSN defined in same node */
if (xmlns_check(xn, nsn) != NULL)
@@ -965,7 +963,7 @@ xml_find_type_value(cxobj *xt,
char *xprefix; /* xprefix */
while ((x = xml_child_each(xt, x, type)) != NULL) {
- xprefix = xml_namespace(x);
+ xprefix = xml_prefix(x);
if (prefix)
pmatch = xprefix?strcmp(prefix,xprefix)==0:0;
else
@@ -1121,7 +1119,7 @@ clicon_xml2file(FILE *f,
if (x == NULL)
goto ok;
name = xml_name(x);
- namespace = xml_namespace(x);
+ namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
@@ -1246,7 +1244,7 @@ clicon_xml2cbuf(cbuf *cb,
char *val;
name = xml_name(x);
- namespace = xml_namespace(x);
+ namespace = xml_prefix(x);
switch(xml_type(x)){
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
@@ -1333,10 +1331,10 @@ xmltree2cbuf(cbuf *cb,
cprintf(cb, " ");
if (xml_type(x) != CX_BODY)
cprintf(cb, "%s", xml_type2str(xml_type(x)));
- if (xml_namespace(x)==NULL)
+ if (xml_prefix(x)==NULL)
cprintf(cb, " %s", xml_name(x));
else
- cprintf(cb, " %s:%s", xml_namespace(x), xml_name(x));
+ cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x));
if (xml_value(x))
cprintf(cb, " value:\"%s\"", xml_value(x));
if (x->x_flags)
@@ -1612,8 +1610,8 @@ xml_copy_one(cxobj *x0,
if ((s = xml_name(x0))) /* malloced string */
if ((xml_name_set(x1, s)) < 0)
return -1;
- if ((s = xml_namespace(x0))) /* malloced string */
- if ((xml_namespace_set(x1, s)) < 0)
+ if ((s = xml_prefix(x0))) /* malloced string */
+ if ((xml_prefix_set(x1, s)) < 0)
return -1;
return 0;
}
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 6794b503c..2263bde27 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -87,6 +87,7 @@
#include "clixon_xpath.h"
#include "clixon_log.h"
#include "clixon_err.h"
+#include "clixon_netconf_lib.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_map.h"
@@ -230,10 +231,15 @@ xml2cli(FILE *f,
/*! Validate xml node of type leafref, ensure the value is one of that path's reference
* @param[in] xt XML leaf node of type leafref
* @param[in] ytype Yang type statement belonging to the XML node
+ * @param[out] cbret Error buffer
+ * @retval 1 Validation OK
+ * @retval 0 Validation failed
+ * @retval -1 Error
*/
static int
validate_leafref(cxobj *xt,
- yang_stmt *ytype)
+ yang_stmt *ytype,
+ cbuf *cbret)
{
int retval = -1;
yang_stmt *ypath;
@@ -247,8 +253,9 @@ validate_leafref(cxobj *xt,
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
- clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
- goto done;
+ if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Leafref requires path statement") < 0)
+ goto done;
+ goto fail;
}
if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 0)
goto done;
@@ -260,9 +267,9 @@ validate_leafref(cxobj *xt,
break;
}
if (i==xlen){
- clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s",
- leafrefbody);
- goto done;
+ if (netconf_bad_element(cbret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0)
+ goto done;
+ goto fail;
}
ok:
retval = 0;
@@ -270,6 +277,9 @@ validate_leafref(cxobj *xt,
if (xvec)
free(xvec);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Validate xml node of type identityref, ensure value is a defined identity
@@ -285,14 +295,18 @@ validate_leafref(cxobj *xt,
* @param[in] xt XML leaf node of type identityref
* @param[in] ys Yang spec of leaf
* @param[in] ytype Yang type field of type identityref
+ * @param[out] cbret Error buffer
+ * @retval 1 Validation OK
+ * @retval 0 Validation failed
+ * @retval -1 Error
* @see ys_populate_identity where the derived types are set
* @see RFC7950 Sec 9.10.2:
-
*/
static int
validate_identityref(cxobj *xt,
yang_stmt *ys,
- yang_stmt *ytype)
+ yang_stmt *ytype,
+ cbuf *cbret)
{
int retval = -1;
char *node;
@@ -305,37 +319,46 @@ validate_identityref(cxobj *xt,
* Always add default prefix because derived identifiers are stored with
* prefixes in the base identifiers derived-list.
*/
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
if ((node = xml_body(xt)) == NULL)
return 0;
if (strchr(node, ':') == NULL){
prefix = yang_find_myprefix(ys);
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
cprintf(cb, "%s:%s", prefix, node);
node = cbuf_get(cb);
}
/* This is the type's base reference */
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){
- clicon_err(OE_DB, 0, "Identityref validation failed, no base");
- goto done;
+ if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Identityref validation failed, no base") < 0)
+ goto done;
+ goto fail;
}
/* This is the actual base identity */
if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){
- clicon_err(OE_DB, 0, "Identityref validation failed, no base identity");
- goto done;
+ if (netconf_missing_element(cbret, "application", ybaseref->ys_argument, "Identityref validation failed, no base identity") < 0)
+ goto done;
+ goto fail;
}
/* Here check if node is in the derived node list of the base identity */
if (cvec_find(ybaseid->ys_cvec, node) == NULL){
- clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument);
- goto done;
+ cbuf_reset(cb);
+ cprintf(cb, "Identityref validation failed, %s not derived from %s",
+ node, ybaseid->ys_argument);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cb)) < 0)
+ goto done;
+ goto fail;
}
- retval = 0;
+ retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Validate an RPC node
@@ -377,24 +400,33 @@ validate_identityref(cxobj *xt,
* the same order as they are defined within the "output" statement.
*/
int
-xml_yang_validate_rpc(cxobj *xrpc)
+xml_yang_validate_rpc(cxobj *xrpc,
+ cbuf *cbret)
{
int retval = -1;
yang_stmt *yn=NULL; /* rpc name */
cxobj *xn; /* rpc name */
- yang_stmt *yi=NULL; /* input name */
- cxobj *xi; /* input name */
+ int ret;
- assert(strcmp(xml_name(xrpc), "rpc")==0);
+ if (strcmp(xml_name(xrpc), "rpc")){
+ clicon_err(OE_XML, EINVAL, "Expected RPC");
+ goto done;
+ }
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;
- xi = NULL;
- while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) {
- if ((yi = xml_spec(xi)) == 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)
+ goto fail;
+ if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0)
+ goto done;
}
// ok: /* pass validation */
retval = 1;
@@ -408,14 +440,24 @@ xml_yang_validate_rpc(cxobj *xrpc)
/*! Validate a single XML node with yang specification for added entry
* 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
- * @retval 0 Valid OK
- * @retval -1 Validation failed
+ * @param[in] xt XML node to be validated
+ * @param[out] cbret Error buffer
+ * @retval 1 Validation OK
+ * @retval 0 Validation failed
+ * @retval -1 Error
+ * @code
+ * cxobj *x;
+ * cbuf *cbret = cbuf_new();
+ * if ((ret = xml_yang_validate_add(x, cbret)) < 0)
+ * err;
+ * if (ret == 0)
+ * fail;
+ * @endcode
* @see xml_yang_validate_all
*/
int
xml_yang_validate_add(cxobj *xt,
- void *arg)
+ cbuf *cbret)
{
int retval = -1;
cg_var *cv = NULL;
@@ -424,11 +466,14 @@ xml_yang_validate_add(cxobj *xt,
int i;
yang_stmt *ys;
char *body;
+ int ret;
+ cxobj *x;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){
switch (ys->ys_keyword){
+ case Y_RPC:
case Y_INPUT:
case Y_LIST:
/* fall thru */
@@ -440,9 +485,9 @@ xml_yang_validate_add(cxobj *xt,
if (yang_config(yc)==0)
continue;
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
- clicon_err(OE_CFG, 0,"Missing mandatory variable: %s",
- yc->ys_argument);
- goto done;
+ if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0)
+ goto done;
+ goto fail;
}
}
break;
@@ -463,12 +508,11 @@ xml_yang_validate_add(cxobj *xt,
goto done;
}
if ((ys_cv_validate(cv, ys, &reason)) != 1){
- clicon_err(OE_DB, 0,
- "validation of %s failed %s",
- ys->ys_argument, reason?reason:"");
+ if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0)
+ goto done;
if (reason)
free(reason);
- goto done;
+ goto fail;
}
}
break;
@@ -476,28 +520,43 @@ xml_yang_validate_add(cxobj *xt,
break;
}
}
- retval = 0;
+ x = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
+ if ((ret = xml_yang_validate_add(x, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ retval = 1;
done:
if (cv)
cv_free(cv);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! 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[in] arg Not used
- * @retval -1 Validation failed
- * @retval 0 Validation OK
+ * @param[out] cbret Error buffer
+ * @retval 1 Validation OK
+ * @retval 0 Validation failed
+ * @retval -1 Error
* @see xml_yang_validate_add
* @code
- * if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0)
+ * cxobj *x;
+ * cbuf *cbret = cbuf_new();
+ * if ((ret = xml_yang_validate_all(x, cbret)) < 0)
* err;
+ * if (ret == 0)
+ * fail;
* @endcode
*/
int
xml_yang_validate_all(cxobj *xt,
- void *arg)
+ cbuf *cbret)
{
int retval = -1;
yang_stmt *ys; /* yang node */
@@ -505,13 +564,24 @@ xml_yang_validate_all(cxobj *xt,
yang_stmt *ye; /* yang must error-message */
char *xpath;
int nr;
-
+ int ret;
+ cxobj *x;
+
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
- if ((ys = xml_spec(xt)) != NULL &&
- yang_config(ys) != 0){
+ ys=xml_spec(xt);
+ if (ys==NULL){
+ if (netconf_unknown_element(cbret, "application", xml_name(xt), NULL) < 0)
+ goto done;
+ goto fail;
+ }
+ if (ys != NULL && yang_config(ys) != 0){
/* Node-specific validation */
switch (ys->ys_keyword){
+ case Y_ANYXML:
+ case Y_ANYDATA:
+ goto ok;
+ break;
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
@@ -520,11 +590,11 @@ xml_yang_validate_all(cxobj *xt,
*/
if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){
if (strcmp(yc->ys_argument, "leafref") == 0){
- if (validate_leafref(xt, yc) < 0)
+ if (validate_leafref(xt, yc, cbret) < 0)
goto done;
}
else if (strcmp(yc->ys_argument, "identityref") == 0){
- if (validate_identityref(xt, ys, yc) < 0)
+ if (validate_identityref(xt, ys, yc, cbret) < 0)
goto done;
}
}
@@ -568,11 +638,11 @@ xml_yang_validate_all(cxobj *xt,
if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0)
goto done;
if (!nr){
- if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL)
- clicon_err(OE_DB, 0, "%s", ye->ys_argument);
- else
- clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
- goto done;
+ ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL);
+ if (netconf_operation_failed(cbret, "application",
+ ye?ye->ys_argument:"must xpath validation failed") < 0)
+ goto done;
+ goto fail;
}
}
/* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
@@ -581,14 +651,42 @@ xml_yang_validate_all(cxobj *xt,
if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0)
goto done;
if (!nr){
- clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
- goto done;
+ if (netconf_operation_failed(cbret, "application",
+ "when xpath validation failed") < 0)
+ goto done;
+ goto fail;
}
}
}
- retval = 0;
+ x = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
+ if ((ret = xml_yang_validate_all(x, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ ok:
+ retval = 1;
done:
return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+
+int
+xml_yang_validate_all_top(cxobj *xt,
+ cbuf *cbret)
+{
+ int ret;
+ cxobj *x;
+
+ x = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
+ if ((ret = xml_yang_validate_all(x, cbret)) < 1)
+ return ret;
+ }
+ return 1;
}
/*! Translate a single xml node to a cligen variable vector. Note not recursive
@@ -1310,6 +1408,11 @@ xml_tree_prune_flagged(cxobj *xt,
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
+ * @param[in] arg Ignored
+ * Typically called in a recursive apply function:
+ * @code
+ * xml_apply(xt, CX_ELMNT, xml_default, NULL);
+ * @endcode
*/
int
xml_default(cxobj *xt,
@@ -1328,7 +1431,8 @@ xml_default(cxobj *xt,
goto done;
}
/* Check leaf defaults */
- if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){
+ if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST ||
+ ys->ys_keyword == Y_INPUT){
for (i=0; iys_len; i++){
y = ys->ys_stmt[i];
if (y->ys_keyword != Y_LEAF)
@@ -1493,9 +1597,9 @@ 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 */
+ // yang_stmt *ya = NULL; /* arg */
cxobj *x;
- cxobj *xi;
+ // cxobj *xi;
int i;
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
@@ -1521,11 +1625,18 @@ xml_spec_populate_rpc(clicon_handle h,
if (y){
xml_spec_set(x, y);
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
}
}
}
diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y
index 7072ed018..7600d249d 100644
--- a/lib/src/clixon_xml_parse.y
+++ b/lib/src/clixon_xml_parse.y
@@ -172,7 +172,7 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya,
goto done;
if ((x = xml_new(name, xp, y)) == NULL)
goto done;
- if (xml_namespace_set(x, prefix) < 0)
+ if (xml_prefix_set(x, prefix) < 0)
goto done;
ya->ya_xelement = x;
retval = 0;
@@ -223,9 +223,9 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
xml_name(x), name);
goto done;
}
- if (xml_namespace(x)!=NULL){
+ if (xml_prefix(x)!=NULL){
clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s",
- xml_namespace(x), xml_name(x), name);
+ xml_prefix(x), xml_name(x), name);
goto done;
}
/* Strip pretty-print. Ad-hoc algorithm
@@ -263,16 +263,16 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
if (strcmp(xml_name(x), name)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s",
- xml_namespace(x),
+ xml_prefix(x),
xml_name(x),
namespace,
name);
goto done;
}
- if (xml_namespace(x)==NULL ||
- strcmp(xml_namespace(x), namespace)){
+ if (xml_prefix(x)==NULL ||
+ strcmp(xml_prefix(x), namespace)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s",
- xml_namespace(x),
+ xml_prefix(x),
xml_name(x),
namespace,
name);
@@ -324,7 +324,7 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya,
if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
- if (prefix && xml_namespace_set(xa, prefix) < 0)
+ if (prefix && xml_prefix_set(xa, prefix) < 0)
goto done;
if (xml_value_set(xa, attval) < 0)
goto done;
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index 7e68d2b83..befb5bcb8 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -118,6 +118,9 @@ xml_child_spec(char *name,
}
else
y = NULL;
+ /* kludge rpc -> input */
+ if (y && y->ys_keyword == Y_RPC && yang_find((yang_node*)y, Y_INPUT, NULL))
+ y = yang_find((yang_node*)y, Y_INPUT, NULL);
*yresult = y;
retval = 0;
done:
diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c
index 8b9af6d05..68c9ce9fe 100644
--- a/lib/src/clixon_yang.c
+++ b/lib/src/clixon_yang.c
@@ -862,7 +862,7 @@ ys_module_by_xml(yang_spec *ysp,
if (ymodp)
*ymodp = NULL;
- prefix = xml_namespace(xt);
+ prefix = xml_prefix(xt);
if (prefix){
/* Get namespace for prefix */
if (xml2ns(xt, prefix, &namespace) < 0)
diff --git a/test/test_cli.sh b/test/test_cli.sh
index 3bf4af77b..aa1379d95 100755
--- a/test/test_cli.sh
+++ b/test/test_cli.sh
@@ -13,8 +13,10 @@ APPNAME=example
. ./lib.sh
cfg=$dir/conf_yang.xml
+# Use yang in example
+
cat < $cfg
-
+
$cfg
/usr/local/share/$APPNAME/yang
/usr/local/share/clixon
@@ -115,7 +117,7 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$"
new "cli rpc"
expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_feature.sh b/test/test_feature.sh
index 4bbb334ca..dfb991727 100755
--- a/test/test_feature.sh
+++ b/test/test_feature.sh
@@ -96,8 +96,7 @@ 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]]>]]>" '^operation-failedapplicationerrorValidation failed]]>]]>$'
-#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?'
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$'
# This test has been broken up into all different modules instead of one large
# reply since the modules change so often
@@ -162,7 +161,7 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_identity.sh b/test/test_identity.sh
index 1acbc7645..14d6b9963 100755
--- a/test/test_identity.sh
+++ b/test/test_identity.sh
@@ -162,7 +162,7 @@ new "Set crypto to foo:bar"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$"
new "netconf validate"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$"
new "cli set crypto to mc:aes"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto mc:aes" 0 "^$"
@@ -182,7 +182,7 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$"
new "cli validate"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_leafref.sh b/test/test_leafref.sh
index 72faaa4eb..6e66c7f35 100755
--- a/test/test_leafref.sh
+++ b/test/test_leafref.sh
@@ -105,8 +105,8 @@ expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>" "^]]>]]>$"
-new "leafref validate XXX shouldnt really be operation-failed, more work in validate code"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed"
+new "leafref validate"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$'
new "leafref discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
@@ -126,7 +126,7 @@ new "leafref delete leaf"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^"
new "leafref validate (should fail)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$'
new "leafref discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
@@ -143,7 +143,7 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$"
new "cli sender template"
expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_list.sh b/test/test_list.sh
index 7e42a23bd..37fb8c6c0 100755
--- a/test/test_list.sh
+++ b/test/test_list.sh
@@ -125,7 +125,7 @@ new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
fi # NYI
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_nacm.sh b/test/test_nacm.sh
index c22159c23..afd1c29d0 100755
--- a/test/test_nacm.sh
+++ b/test/test_nacm.sh
@@ -141,10 +141,10 @@ new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
+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"}}}
'
new2 "auth get (wrong passwd: access denied)"
-expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
+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}
@@ -164,21 +164,21 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x
'
new2 "guest get nacm"
-expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}}
'
+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"}}}
'
new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}}
'
+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"}}}
'
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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}}
'
+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"}}}
'
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh
index 1db3949f5..2701b2dab 100755
--- a/test/test_nacm_ext.sh
+++ b/test/test_nacm_ext.sh
@@ -170,10 +170,10 @@ new "Set x to 0"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
+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"}}}
'
new2 "auth get (wrong passwd: access denied)"
-expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}}
'
+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}
@@ -188,16 +188,16 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x
'
new2 "guest get nacm"
-expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}}
'
+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"}}}
'
new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}}
'
+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"}}}
'
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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}}
'
+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"}}}
'
new "cli show conf as admin"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
@@ -220,7 +220,7 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh
index 2ab70b4b6..aebaa8433 100755
--- a/test/test_nacm_protocol.sh
+++ b/test/test_nacm_protocol.sh
@@ -147,7 +147,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
-sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data &
+sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data &
sleep $RCWAIT
@@ -166,20 +166,20 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x"
# Rule 1: deny-kill-session
new "deny-kill-session: limited fail (netconf)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$"
new "deny-kill-session: guest fail (netconf)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$"
new "deny-kill-session: admin ok (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "44]]>]]>" "^]]>]]>$"
# Rule 2: deny-delete-config
new "deny-delete-config: limited fail (netconf)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$"
new2 "deny-delete-config: guest fail (restconf)"
-expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}}
'
+expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}}
'
# In restconf delete-config is translated to edit-config which is permitted
new "deny-delete-config: limited fail (restconf) ok"
@@ -207,12 +207,12 @@ new "permit-edit-config: limited ok restconf"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/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-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}}
'
+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"}}}
'
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_netconf.sh b/test/test_netconf.sh
index a0eae7d80..7b060875b 100755
--- a/test/test_netconf.sh
+++ b/test/test_netconf.sh
@@ -209,7 +209,7 @@ 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]]>]]>" "^invalid-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]]>]]>$"
@@ -254,7 +254,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>"
new "netconf client-side rpc"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_order.sh b/test/test_order.sh
index a06a036b8..cb9eb7801 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -139,7 +139,7 @@ new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$"
new "delete candidate"
-expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$"
# LEAF_LISTS
@@ -172,7 +172,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_perf.sh b/test/test_perf.sh
index d03fcbdee..66c7a5b1e 100755
--- a/test/test_perf.sh
+++ b/test/test_perf.sh
@@ -173,7 +173,7 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index f98b87e42..03d20bd93 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -52,14 +52,6 @@ module example{
}
rpc empty {
}
- rpc input {
- input {
- }
- }
- rpc output {
- output {
- }
- }
rpc client-rpc {
description "Example local client-side rpc";
input {
@@ -104,12 +96,15 @@ new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
-sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data &
+sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data &
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)" "
@@ -125,12 +120,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
'
new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)"
-expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}}
+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}}
'
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"
@@ -161,6 +156,9 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK"
new "restconf empty rpc"
expecteq "$(curl -s -X POST -d {\"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"}}}
'
+
new2 "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
@@ -170,7 +168,7 @@ expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state":
'
new2 "restconf get empty config + state json with wrong module name"
-expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","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 yang node found: badmodule:state"}}}
'
new "restconf get empty config + state xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state)
@@ -215,10 +213,10 @@ 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'
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-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
+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"}}}'
# 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-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
+#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"}}
@@ -244,13 +242,13 @@ expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op":
'
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-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}
'
+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"}}}
'
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)" ""
new "Add nothing using POST"
-expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:'
+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:'
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}]}}
@@ -263,7 +261,7 @@ 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-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}}
'
+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"}}}
'
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)" ""
@@ -273,37 +271,38 @@ expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces
'
new2 "restconf rpc using POST json"
-expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
+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"}}}
'
# 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": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}}
'
+#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"}}}
'
-new2 "restconf rpc using POST json w/o mandatory element"
-expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}}
'
-new2 "restconf rpc non-existing rpc w/o namespace"
-expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}}
'
+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 rpc non-existing rpc without namespace"
+expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}}
'
new2 "restconf rpc non-existing rpc"
-expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}}
'
+expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}}
'
new2 "restconf rpc missing name"
-expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}}
'
+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": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}}
'
+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"}}}
'
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=""
+expect=""
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": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}}
'
+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"}}}
'
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)
@@ -321,7 +320,7 @@ fi
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index dbddee093..ee8200822 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -84,16 +84,16 @@ new "restconf GET if-type"
expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}'
new "restconf POST interface without mandatory type"
-expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '"error-message": "Missing mandatory variable: 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"}}}
'
new "restconf POST interface"
expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 ""
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-tag": "data-exists","error-type": "application","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/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-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}
'
+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"}}}
'
new "restconf DELETE"
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 ""
@@ -138,12 +138,12 @@ new "restconf PUT add interface"
expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/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": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","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/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"
-if [ $BE -ne 0 ]; then
+if [ $BE -eq 0 ]; then
exit # BE
fi
diff --git a/test/test_rpc.sh b/test/test_rpc.sh
new file mode 100755
index 000000000..b1b042518
--- /dev/null
+++ b/test/test_rpc.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+# RPC tests
+# Validate parameters in restconf and netconf, check namespaces, etc
+# See rfc8040 3.6
+APPNAME=example
+
+# include err() and new() functions and creates $dir
+. ./lib.sh
+cfg=$dir/conf.xml
+
+# Use yang in example
+cat < $cfg
+
+ $cfg
+ /usr/local/share/$APPNAME/yang
+ /usr/local/share/clixon
+ $APPNAME
+ /usr/local/lib/$APPNAME/backend
+ /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
+
+EOF
+
+new "test params: -f $cfg"
+if [ $BE -ne 0 ]; then
+ new "kill old backend"
+ sudo clixon_backend -zf $cfg
+ if [ $? -ne 0 ]; then
+ err
+ fi
+ new "start backend -s init -f $cfg"
+ sudo $clixon_backend -s init -f $cfg -D $DBG
+ if [ $? -ne 0 ]; then
+ err
+ 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" -s /bin/sh www-data &
+
+sleep $RCWAIT
+
+new "rpc tests"
+
+# 1.First some positive tests vary media types
+#
+new2 "restconf example rpc json/json default - no headers"
+expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/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"}}
+
'
+
+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"}}
+
'
+
+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"}}
+
'
+
+new2 "restconf example rpc json/xml"
+#
+
'
+
+new2 "restconf example rpc xml/xml"
+#