diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63c5c63f..57ab07c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,15 @@
## 3.10.0/4.0.0 (Upcoming)
### Major New features
+* Yang "min-element" and "max-element" feature supported
+ * According to RFC 7950 7.7.4 and 7.7.5
+ * See (tests)[test/test_minmax.sh]
+ * The following cornercases are not supported:
+ * Check for min-elements>0 for empty lists on top-level
+ * Check for min-elements>0 for empty lists in choice/case
+* Yang "unique" feature supported
+ * According to RFC 7950 7.8.3
+ * See (tests)[test/test_unique.sh]
* Persistent CLI history: [Preserve CLI command history across sessions. The up/down arrows](https://github.com/clicon/clixon/issues/79)
* The design is similar to bash history:
* The CLI loads/saves its complete history to a file on entry and exit, respectively
@@ -45,6 +54,11 @@
### API changes on existing features (you may need to change your code)
* Non-key list now not accepted in edit-config (before only on validation)
+* Changed return values in internal functions
+ * These functions are affected: `netconf_trymerge`, `startup_module_state`, `yang_modules_state_get`
+ * They now comply to Clixon validation: Error: -1; Invalid: 0; OK: 1.
+* New Clixon Yang RPC: ping. To check if backup is running.
+ * Try with ` ]]>]]>`
* Restconf with startup feature will now copy all edit changes to startup db (as it should according to RFC 8040)
* Netconf Startup feature is no longer hardcoded, you need to explicitly enable it (See RFC 6241, Section 8.7)
* Enable in config file with: `ietf-netconf:startup `, or use `*:*`
@@ -116,6 +130,8 @@
### Minor changes
* New XMLDB_FORMAT added: `tree`. An experimental record-based tree database for direct access of records.
+* Netconf error handling modified
+ * New option -e added. If set, the netconf client returns -1 on error.
* A new minimal "hello world" example has been added
* Experimental customized error output strings, see [lib/clixon/clixon_err_string.h]
* Empty leaf values, eg are now checked at validation.
@@ -143,6 +159,9 @@
* Added libgen.h for baseline()
### Corrected Bugs
+* Fixed support for multiple datanodes in a choice/case statement. Only single datanode was supported.
+* Fixed an ordering problem showing up in validate/commit callbacks. If two new items following each order (yang-wise), only the first showed up in the new-list. Thanks achernavin!
+* Fixed a problem caused by recent sorting patches that made "ordered-by user" lists fail in some cases, causing multiple list entries with same keys. NACM being one example. Thanks vratnikov!
* [Restconf does not handle startup datastore according to the RFC](https://github.com/clicon/clixon/issues/74)
* Failure in startup with -m startup or running left running_db cleared.
* Running-db should not be changed on failure. Unless failure-db defined. Or if SEGV, etc. In those cases, tmp_db should include the original running-db.
diff --git a/README.md b/README.md
index 5d195282..36c29745 100644
--- a/README.md
+++ b/README.md
@@ -111,13 +111,11 @@ Clixon follows:
However, the following YANG syntax modules are not implemented:
- deviation
-- min/max-elements
-- unique
- action
- refine
-- Yang extended Xpath functions: re-match, deref, derived-from, derived-from-or-self, enum-value, bit-is-set
-Restrictions on Yang types are as follows:
+The following restrictions on Yang exists:
+- Yang extended Xpath functions: re-match, deref, derived-from, derived-from-or-self, enum-value, bit-is-set
- Submodules cannot re-use a prefix in an import statement that is already used for another imported module in the module that the submodule belongs to. (see https://github.com/clicon/clixon/issues/60)
- Default values on leaf-lists are not supported (RFC7950 7.7.2)
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 73361f8d..c5a208ff 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -158,8 +158,8 @@ backend_client_rm(clicon_handle h,
* @param[in] top Top symbol, ie netconf or restconf-state
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
- * @retval 0 OK
- * @retval 1 Statedata callback failed
+ * @retval 0 Statedata callback failed
+ * @retval 1 OK
*/
static int
client_get_streams(clicon_handle h,
@@ -174,6 +174,7 @@ client_get_streams(clicon_handle h,
yang_stmt *yns = NULL; /* yang namespace */
cxobj *x = NULL;
cbuf *cb = NULL;
+ int ret;
if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s yang module not found", module);
@@ -195,16 +196,22 @@ client_get_streams(clicon_handle h,
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
- retval = 1;
- goto done;
+ goto fail;
}
- retval = netconf_trymerge(x, yspec, xret);
+ if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ retval = 1;
done:
if (cb)
cbuf_free(cb);
if (x)
xml_free(x);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Get system state-data, including streams and plugins
@@ -212,8 +219,8 @@ client_get_streams(clicon_handle h,
* @param[in] xpath Xpath selection, not used but may be to filter early
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
- * @retval 0 OK
- * @retval 1 Statedata callback failed (clicon_err called)
+ * @retval 0 Statedata callback failed (clicon_err called)
+ * @retval 1 OK
*/
static int
client_statedata(clicon_handle h,
@@ -225,22 +232,34 @@ client_statedata(clicon_handle h,
size_t xlen;
int i;
yang_stmt *yspec;
+ int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
- if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
- if ((retval = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) != 0)
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
+ if ((ret = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) < 0)
goto done;
- if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040"))
- if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
+ if (ret == 0)
+ goto fail;
+ }
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
+ if ((ret = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) < 0)
goto done;
- if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
- if ((retval = yang_modules_state_get(h, yspec, xpath, 0, xret)) != 0)
+ if (ret == 0)
+ goto fail;
+ }
+ if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
+ if ((ret = yang_modules_state_get(h, yspec, xpath, 0, xret)) < 0)
goto done;
- if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
+ if (ret == 0)
+ goto fail;
+ }
+ if ((ret = clixon_plugin_statedata(h, yspec, xpath, xret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
/* Code complex to filter out anything that is outside of xpath */
if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@@ -259,12 +278,15 @@ client_statedata(clicon_handle h,
/* reset flag */
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
- retval = 0; /* OK */
+ retval = 1; /* OK */
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (xvec)
free(xvec);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Retrieve all or part of a specified configuration.
@@ -789,7 +811,7 @@ from_client_get(clicon_handle h,
clicon_err_reset();
if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done;
- if (ret == 1){ /* Error from callback (error in xret) */
+ if (ret == 0){ /* Error from callback (error in xret) */
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done;
goto ok;
@@ -1045,6 +1067,26 @@ from_client_debug(clicon_handle h,
return retval;
}
+/*! Check liveness of backend daemon, just send a reply
+ * @param[in] h Clicon handle
+ * @param[in] xe Request:
+ * @param[out] cbret Return xml tree, eg ..., ");
+ return 0;
+}
+
/*! An internal clicon message has arrived from a client. Receive and dispatch.
* @param[in] h Clicon handle
* @param[in] s Socket where message arrived. read from this.
@@ -1277,10 +1319,13 @@ backend_rpc_init(clicon_handle h)
if (rpc_callback_register(h, from_client_create_subscription, NULL,
"urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0)
goto done;
- /* In backend_client.? Clixon RPC */
+ /* Clixon RPC */
if (rpc_callback_register(h, from_client_debug, NULL,
"http://clicon.org/lib", "debug") < 0)
goto done;
+ if (rpc_callback_register(h, from_client_ping, NULL,
+ "http://clicon.org/lib", "ping") < 0)
+ goto done;
retval =0;
done:
return retval;
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
index 19b98c7e..de04d161 100644
--- a/apps/backend/backend_plugin.c
+++ b/apps/backend/backend_plugin.c
@@ -115,8 +115,8 @@ clixon_plugin_reset(clicon_handle h,
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error
- * @retval 0 OK
- * @retval 1 Statedata callback failed (xret set with netconf-error)
+ * @retval 0 Statedata callback failed (xret set with netconf-error)
+ * @retval 1 OK
* @note xtop can be replaced
*/
int
@@ -136,24 +136,25 @@ clixon_plugin_statedata(clicon_handle h,
continue;
if ((x = xml_new("config", NULL, NULL)) == NULL)
goto done;
- if (fn(h, xpath, x) < 0){
- retval = 1;
- goto done; /* Dont quit here on user callbacks */
- }
- if ((ret = netconf_trymerge(x, yspec, xret)) != 0){
- retval = ret;
+ if (fn(h, xpath, x) < 0)
+ goto fail; /* Dont quit here on user callbacks */
+ if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
- }
+ if (ret == 0)
+ goto fail;
if (x){
xml_free(x);
x = NULL;
}
}
- retval = 0;
+ retval = 1;
done:
if (x)
xml_free(x);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Create and initialize transaction */
diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c
index 96195f31..f2270644 100644
--- a/apps/backend/backend_startup.c
+++ b/apps/backend/backend_startup.c
@@ -331,6 +331,8 @@ startup_failsafe(clicon_handle h)
/*! Init modules state of the backend (server). To compare with startup XML
* Set the modules state as setopt to the datastore module.
* Only if CLICON_XMLDB_MODSTATE is enabled
+ * @retval -1 Error
+ * @retval 0 OK
*/
int
startup_module_state(clicon_handle h,
@@ -338,17 +340,23 @@ startup_module_state(clicon_handle h,
{
int retval = -1;
cxobj *x = NULL;
+ int ret;
if (!clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
goto ok;
/* Set up cache
* Now, access brief module cache with clicon_modst_cache_get(h, 1) */
- if (yang_modules_state_get(h, yspec, NULL, 1, &x) < 0)
+ if ((ret = yang_modules_state_get(h, yspec, NULL, 1, &x)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
ok:
- retval = 0;
+ retval = 1;
done:
if (x)
xml_free(x);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index f85bba32..68d82705 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -71,10 +71,13 @@
#include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */
-#define NETCONF_OPTS "hD:f:l:qa:u:d:p:y:U:t:o:"
+#define NETCONF_OPTS "hD:f:l:qa:u:d:p:y:U:t:eo:"
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
+/*! Ignore errors on packet errors: continue */
+static int ignore_packet_errors = 1;
+
/*! Process incoming packet
* @param[in] h Clicon handle
* @param[in] cb Packet buffer
@@ -235,8 +238,9 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */
/* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
- if (netconf_input_packet(h, cb) < 0)
- ; //goto done; // ignore errors
+ if (netconf_input_packet(h, cb) < 0 &&
+ !ignore_packet_errors) // default is to ignore errors
+ goto done;
if (cc_closed)
break;
cbuf_reset(cb);
@@ -338,6 +342,7 @@ usage(clicon_handle h,
"\t-y \tLoad yang spec file (override yang main module)\n"
"\t-U \tOver-ride unix user with a pseudo user for NACM.\n"
"\t-t \tTimeout in seconds. Quit after this time.\n"
+ "\t-e \tDont ignore errors on packet input.\n"
"\t-o \"=\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
clicon_netconf_dir(h)
@@ -349,6 +354,7 @@ int
main(int argc,
char **argv)
{
+ int retval = -1;
int c;
char *argv0 = argv[0];
int quiet = 0;
@@ -455,6 +461,9 @@ main(int argc,
case 't': /* timeout in seconds */
tv.tv_sec = atoi(optarg);
break;
+ case 'e': /* dont ignore packet errors */
+ ignore_packet_errors = 0;
+ break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
@@ -528,9 +537,12 @@ main(int argc,
}
if (event_loop() < 0)
goto done;
+ retval = 0;
done:
+ if (ignore_packet_errors)
+ retval = 0;
netconf_terminate(h);
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
- return 0;
+ return retval;
}
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index d9df89cd..c2290331 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -588,6 +588,10 @@ api_data_post(clicon_handle h,
goto done;
goto ok;
}
+ if (xretcom){ /* Clear: can be reused again below */
+ xml_free(xretcom);
+ xretcom = NULL;
+ }
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
@@ -933,6 +937,10 @@ api_data_put(clicon_handle h,
goto done;
goto ok;
}
+ if (xretcom){ /* Clear: can be reused again below */
+ xml_free(xretcom);
+ xretcom = NULL;
+ }
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
@@ -1108,6 +1116,10 @@ api_data_delete(clicon_handle h,
goto done;
goto ok;
}
+ if (xretcom){ /* Clear: can be reused again below */
+ xml_free(xretcom);
+ xretcom = NULL;
+ }
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h
index 01a7ee2f..2953ab5f 100644
--- a/lib/clixon/clixon_netconf_lib.h
+++ b/lib/clixon/clixon_netconf_lib.h
@@ -66,6 +66,8 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message);
int netconf_operation_failed_xml(cxobj **xret, char *type, char *message);
int netconf_malformed_message(cbuf *cb, char *message);
int netconf_malformed_message_xml(cxobj **xret, char *message);
+int netconf_data_not_unique(cbuf *cb, cxobj *x, cvec *cvk);
+int netconf_minmax_elements(cbuf *cb, cxobj *x, int max);
int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret);
int netconf_module_load(clicon_handle h);
char *netconf_db_find(cxobj *xn, char *name);
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index 45956aa9..b02fa04d 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -966,13 +966,90 @@ netconf_malformed_message_xml(cxobj **xret,
return retval;
}
+/*! Create Netconf data-not-unique error message according to RFC 7950 15.1
+ *
+ * A NETCONF operation would result in configuration data where a
+ * "unique" constraint is invalidated.
+ * @param[out] cb CLIgen buf. Error XML is written in this buffer
+ * @param[in] x List element containing duplicate
+ * @param[in] cvk List of comonents in x that are non-unique
+ * @see RFC7950 Sec 15.1
+ */
+int
+netconf_data_not_unique(cbuf *cb,
+ cxobj *x,
+ cvec *cvk)
+{
+ int retval = -1;
+ cg_var *cvi = NULL;
+ cxobj *xi;
+
+ if (cprintf(cb, ""
+ "protocol "
+ "operation-failed "
+ "data-not-unique "
+ "error "
+ "") < 0)
+ goto err;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL){
+ if ((xi = xml_find(x, cv_string_get(cvi))) == NULL)
+ continue; /* ignore, shouldnt happen */
+ cprintf(cb, "");
+ clicon_xml2cbuf(cb, xi, 0, 0);
+ cprintf(cb, " ");
+ }
+ if (cprintf(cb, " ") <0)
+ goto err;
+ retval = 0;
+ done:
+ return retval;
+ err:
+ clicon_err(OE_XML, errno, "cprintf");
+ goto done;
+}
+
+/*! Create Netconf too-many/few-elements err msg according to RFC 7950 15.2/15.3
+ *
+ * A NETCONF operation would result in configuration data where a
+ list or a leaf-list would have too many entries, the following error
+ * @param[out] cb CLIgen buf. Error XML is written in this buffer
+ * @param[in] x List element containing duplicate
+ * @param[in] max If set, return too-many, otherwise too-few
+ * @see RFC7950 Sec 15.1
+ */
+int
+netconf_minmax_elements(cbuf *cb,
+ cxobj *x,
+ int max)
+{
+ int retval = -1;
+
+ if (cprintf(cb, ""
+ "protocol "
+ "operation-failed "
+ "too-%s-elements "
+ "error "
+ "%s "
+ " ",
+ max?"many":"few",
+ xml_name(x)) < 0) /* XXX should be xml2xpath */
+ goto err;
+ retval = 0;
+ done:
+ return retval;
+ err:
+ clicon_err(OE_XML, errno, "cprintf");
+ goto done;
+}
+
+
/*! Help function: merge - check yang - if error make netconf errmsg
* @param[in] x XML tree
* @param[in] yspec Yang spec
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
- * @retval 0 OK
- * @retval 1 Statedata callback failed
+ * @retval 0 Statedata callback failed
+ * @retval 1 OK
*/
int
netconf_trymerge(cxobj *x,
@@ -995,15 +1072,17 @@ netconf_trymerge(cxobj *x,
xml_purge(xc);
if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
goto done;
- retval = 1;
- goto done;
+ goto fail;
}
ok:
- retval = 0;
+ retval = 1;
done:
if (reason)
free(reason);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! Load ietf netconf yang module and set enabled features
diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c
index d8f57149..0f2fd622 100644
--- a/lib/src/clixon_plugin.c
+++ b/lib/src/clixon_plugin.c
@@ -503,7 +503,6 @@ rpc_callback_call(clicon_handle h,
void *arg)
{
int retval = -1;
- int ret;
rpc_callback_t *rc;
char *name;
char *prefix;
@@ -520,7 +519,7 @@ rpc_callback_call(clicon_handle h,
if (strcmp(rc->rc_name, name) == 0 &&
namespace && rc->rc_namespace &&
strcmp(rc->rc_namespace, namespace) == 0){
- if ((ret = rc->rc_callback(h, xe, cbret, arg, rc->rc_arg)) < 0){
+ if (rc->rc_callback(h, xe, cbret, arg, rc->rc_arg) < 0){
clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
goto done;
}
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index e799b166..d94f2641 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -495,6 +495,7 @@ xml_yang_validate_rpc(cxobj *xrpc,
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
+ * Check if xt is part of valid choice
*/
static int
check_choice(cxobj *xt,
@@ -502,27 +503,59 @@ check_choice(cxobj *xt,
cbuf *cbret)
{
int retval = -1;
- yang_stmt *yc;
yang_stmt *y;
+ yang_stmt *ytp; /* yt:s parent */
+ yang_stmt *ytcase = NULL; /* yt:s parent case if any */
+ yang_stmt *ytchoice = NULL;
yang_stmt *yp;
cxobj *x;
cxobj *xp;
- if ((yc = yang_choice(yt)) == NULL)
+ if ((ytp = yang_parent_get(yt)) == NULL)
goto ok;
+ /* Return OK if xt is not choice */
+ switch (yang_keyword_get(ytp)){
+ case Y_CASE:
+ ytcase = ytp;
+ ytchoice = yang_parent_get(ytp);
+ break;
+ case Y_CHOICE:
+ ytchoice = ytp;
+ break;
+ default:
+ goto ok; /* Not choice */
+ break;
+ }
if ((xp = xml_parent(xt)) == NULL)
goto ok;
- x = NULL; /* Find a child with same yang spec */
+ x = NULL; /* Find a child with same yang spec */
while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
- if ((x != xt) &&
- (y = xml_spec(x)) != NULL &&
- (yp = yang_choice(y)) != NULL &&
- yp == yc){
- if (netconf_bad_element(cbret, "application", xml_name(x), "Element in choice statement already exists") < 0)
- goto done;
- goto fail;
+ if (x == xt)
+ continue;
+ y = xml_spec(x);
+ if (y == yt) /* eg same list */
+ continue;
+ yp = yang_parent_get(y);
+ switch (yang_keyword_get(yp)){
+ case Y_CASE:
+ if (yang_parent_get(yp) != ytchoice) /* Not same choice (not releveant) */
+ continue;
+ if (yp == ytcase) /* same choice but different case */
+ continue;
+ break;
+ case Y_CHOICE:
+ if (yp != ytcase) /* Not same choice (not relevant) */
+ continue;
+ break;
+ default:
+ continue; /* not choice */
+ break;
}
- }
+ if (netconf_bad_element(cbret, "application", xml_name(x), "Element in choice statement already exists") < 0)
+ goto done;
+ goto fail;
+ } /* while */
+
ok:
retval = 1;
done:
@@ -671,6 +704,311 @@ check_list_key(cxobj *xt,
goto done;
}
+/*! New element last in list, check if already exists if sp return -1
+ * @param[in] vec Vector of existing entries (new is last)
+ * @param[in] i1 The new entry is placed at vec[i1]
+ * @param[in] vlen Lenght of entry
+ * @retval 0 OK, entry is unique
+ * @retval -1 Duplicate detected
+ * @note This is currently linear complexity. It could be improved by inserting new element sorted and binary search.
+ */
+static int
+check_insert_duplicate(char **vec,
+ int i1,
+ int vlen)
+{
+ int i;
+ int v;
+ char *b;
+
+ for (i=0; i 0 && /* 0 means unbounded */
+ nr > cv_uint32_get(cv)){
+ if (netconf_minmax_elements(cbret, x, 1) < 0)
+ goto done;
+ goto fail;
+ }
+ }
+ retval = 1;
+ done:
+ return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+
+/*! Detect unique constraint for duplicates from parent node and minmax
+ * @param[in] xt XML parent (may have lists w unique constraints as child)
+ * @param[out] cbret Error buffer (set w netconf error if retval == 0)
+ * @retval 1 Validation OK
+ * @retval 0 Validation failed (cbret set)
+ * @retval -1 Error
+ * Assume xt:s children are sorted and yang populated.
+ * The function does two different things of the children of an XML node:
+ * (1) Check min/max element constraints
+ * (2) Check unique constraints
+ *
+ * The routine uses a node traversing mechanism as the following example, where
+ * two lists [x1,..] and [x2,..] are embedded:
+ * xt: {a, b, [x1, x1, x1], d, e, f, [x2, x2, x2], g}
+ * The function does this using a single iteration and uses the fact that the
+ * xml symbols share yang symbols: ie [x1..] has yang y1 and d has yd.
+ *
+ * Unique constraints:
+ * Lists are identified, then check_unique_list is called on each list.
+ * Example, x has an associated yang list node with list of unique constraints
+ * y-list->y-unique - "a"
+ * xt->x -> ab
+ * x -> bc
+ * x -> ab
+ *
+ * Min-max constraints:
+ * Find upper and lower bound of existing lists and report violations
+ * Somewhat tricky to find violation of min-elements of empty
+ * lists, but this is done by a "gap-detection" mechanism, which detects
+ * gaps in the xml nodes given the ancestor Yang structure.
+ * But no gap analysis is done if the yang spec of the top-level xml is unknown
+ * Example:
+ * Yang structure:y1, y2, y3,
+ * XML structure: [x1, x1], [x3, x3] where [x2] list is missing
+ * @note min-element constraints on empty lists are not detected on top-level.
+ * Or more specifically, if no yang spec if associated with the top-level
+ * XML node. This may not be a large problem since it would mean empty configs
+ * are not allowed.
+ */
+static int
+check_list_unique_minmax(cxobj *xt,
+ cbuf *cbret)
+{
+ int retval = -1;
+ cxobj *x = NULL;
+ yang_stmt *y;
+ yang_stmt *yt;
+ yang_stmt *yp = NULL; /* previous in list */
+ yang_stmt *ye = NULL; /* yang each list to catch emtpy */
+ yang_stmt *ych; /* y:s parent node (if choice that ye can compare to) */
+ cxobj *xp = NULL; /* previous in list */
+ yang_stmt *yu; /* yang unique */
+ int ret;
+ int nr=0; /* Nr of list elements for min/max check */
+ enum rfc_6020 keyw;
+
+ /* RFC 7950 7.7.5: regarding min-max elements check
+ * The behavior of the constraint depends on the type of the
+ * leaf-list's or list's closest ancestor node in the schema tree
+ * that is not a non-presence container (see Section 7.5.1):
+ * o If no such ancestor exists in the schema tree, the constraint
+ * is enforced.
+ * o Otherwise, if this ancestor is a case node, the constraint is
+ * enforced if any other node from the case exists.
+ * o Otherwise, it is enforced if the ancestor node exists.
+ */
+ yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */
+ /* Traverse all elemenents */
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
+ if ((y = xml_spec(x)) == NULL)
+ continue;
+ if ((ych=yang_choice(y)) == NULL)
+ ych = y;
+ keyw = yang_keyword_get(y);
+ if (keyw != Y_LIST && keyw != Y_LEAF_LIST)
+ continue;
+ if (yp != NULL){ /* There exists a previous (leaf)list */
+ if (y == yp){ /* If same yang as previous x, then skip (eg same list) */
+ nr++;
+ continue;
+ }
+ else {
+ /* Check if the list length violates min/max */
+ if ((ret = check_min_max(xp, yp, nr, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ }
+ yp = y; /* Restart min/max count */
+ xp = x; /* Need a reference to the XML as well */
+ nr = 1;
+ /* Gap analysis: Check if there is any empty list between y and yp
+ * Note, does not detect empty choice list (too complicated)
+ */
+ if (yt != NULL && ych != ye){
+ /* Skip analysis if Yang spec is unknown OR
+ * if we are still iterating the same Y_CASE w multiple lists
+ */
+ ye = yn_each(yt, ye);
+ if (ye && ych != ye)
+ do {
+ if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){
+ /* Check if the list length violates min/max */
+ if ((ret = check_min_max(xt, ye, 0, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ ye = yn_each(yt, ye);
+ } while(ye != NULL && /* to avoid livelock (shouldnt happen) */
+ ye != ych);
+ }
+ if (keyw != Y_LIST)
+ continue;
+ /* Here only lists. test unique constraints */
+ yu = NULL;
+ while ((yu = yn_each(y, yu)) != NULL) {
+ if (yang_keyword_get(yu) != Y_UNIQUE)
+ continue;
+ /* Here is a list w unique constraints identified by:
+ * its first element x, its yang spec y, its parent xt, and
+ * a unique yang spec yu,
+ */
+ if ((ret = check_unique_list(x, xt, y, yu, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ }
+ /* yp if set, is a list that has been traversed
+ * This check is made in the loop as well - this is for the last list
+ */
+ if (yp){
+ /* Check if the list length violates min/max */
+ if ((ret = check_min_max(xp, yp, nr, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ /* Check if there is any empty list between after last non-empty list
+ * Note, does not detect empty lists within choice/case (too complicated)
+ */
+ if ((ye = yn_each(yt, ye)) != NULL)
+ do {
+ if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){
+ /* Check if the list length violates min/max */
+ if ((ret = check_min_max(xt, ye, 0, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ } while((ye = yn_each(yt, ye)) != NULL);
+ retval = 1;
+ done:
+ return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+
/*! 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.
@@ -850,9 +1188,9 @@ xml_yang_validate_all(cxobj *xt,
goto done;
goto fail;
}
- if (ys != NULL && yang_config(ys) != 0){
+ if (yang_config(ys) != 0){
/* Node-specific validation */
- switch (ys->ys_keyword){
+ switch (yang_keyword_get(ys)){
case Y_ANYXML:
case Y_ANYDATA:
goto ok;
@@ -872,33 +1210,6 @@ xml_yang_validate_all(cxobj *xt,
if (validate_identityref(xt, ys, yc, cbret) < 0)
goto done;
}
- }
- if ((yc = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
- /* The behavior of the constraint depends on the type of the
- * leaf-list's or list's closest ancestor node in the schema tree
- * that is not a non-presence container (see Section 7.5.1):
- * o If no such ancestor exists in the schema tree, the constraint
- * is enforced.
- * o Otherwise, if this ancestor is a case node, the constraint is
- * enforced if any other node from the case exists.
- * o Otherwise, it is enforced if the ancestor node exists.
- */
-#if 0
- cxobj *xp;
- cxobj *x;
- int i;
-
- if ((xp = xml_parent(xt)) != NULL){
- nr = atoi(yc->ys_argument);
- x = NULL;
- i = 0;
- while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL)
- i++;
- }
-#endif
- }
- if ((yc = yang_find(ys, Y_MAX_ELEMENTS, NULL)) != NULL){
-
}
break;
default:
@@ -941,6 +1252,14 @@ xml_yang_validate_all(cxobj *xt,
if (ret == 0)
goto fail;
}
+ /* Check unique and min-max after choice test for example*/
+ if (yang_config(ys) != 0){
+ /* Checks if next level contains any unique list constraints */
+ if ((ret = check_list_unique_minmax(xt, cbret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
ok:
retval = 1;
done:
@@ -961,12 +1280,14 @@ xml_yang_validate_all_top(cxobj *xt,
{
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;
}
+ if ((ret = check_list_unique_minmax(xt, cbret)) < 1)
+ return ret;
return 1;
}
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index fb063250..e3cef110 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -416,11 +416,9 @@ xml_search1(cxobj *xp,
cmp = yangi-yang_order(y);
/* Here is right yang order == same yang? */
if (cmp == 0){
- if (userorder){
+ cmp = xml_cmp(x1, xc, 0);
+ if (cmp && userorder) /* Ordered by user (if not equal) */
return xml_search_userorder(xp, x1, y, yangi, mid);
- }
- else /* Ordered by system */
- cmp = xml_cmp(x1, xc, 0);
}
if (cmp == 0)
return xc;
diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c
index 525fc51b..07e5eea4 100644
--- a/lib/src/clixon_yang.c
+++ b/lib/src/clixon_yang.c
@@ -759,12 +759,12 @@ yang_choice(yang_stmt *y)
yang_stmt *yp;
if ((yp = y->ys_parent) != NULL){
- switch (yp->ys_keyword){
+ switch (yang_keyword_get(yp)){
case Y_CHOICE:
return yp;
break;
case Y_CASE:
- return yp->ys_parent;
+ return yang_parent_get(yp);
break;
default:
break;
@@ -773,31 +773,57 @@ yang_choice(yang_stmt *y)
return NULL;
}
+/*! Find matching y in yp:s children, "yang order" of y when y is choice
+ * @param[in] yp Choice node
+ * @param[in] y Yang datanode to find
+ * @param[out] index Index of y in yp:s list of children
+ * @retval 0 not found (must be datanode)
+ * @retval 1 found
+ * @see order1 the main function
+ * There are two distinct cases, either (1) the choice has case statements, or
+ * (2) it uses shortcut mode without case statements.
+ * In (1) we need to count how many sub-statements and keep a max
+ * In (2) we increment with only 1.
+ */
static int
order1_choice(yang_stmt *yp,
- yang_stmt *y)
+ yang_stmt *y,
+ int *index)
{
yang_stmt *ys;
yang_stmt *yc;
int i;
int j;
+ int shortcut=0;
+ int max=0;
- for (i=0; iys_len; i++){
+ for (i=0; iys_len; i++){ /* Loop through choice */
ys = yp->ys_stmt[i];
- if (ys->ys_keyword == Y_CASE){
+ if (ys->ys_keyword == Y_CASE){ /* Loop through case */
for (j=0; jys_len; j++){
yc = ys->ys_stmt[j];
- if (yang_datanode(yc) && yc == y)
+ if (yang_datanode(yc) && yc == y){
+ *index += j;
return 1;
+ }
}
+ if (j>max)
+ max = j;
+ }
+ else {
+ shortcut = 1; /* Shortcut, no case */
+ if (yang_datanode(ys) && ys == y)
+ return 1;
}
- else if (yang_datanode(ys) && ys == y)
- return 1;
}
+ if (shortcut)
+ (*index)++;
+ else
+ *index += max;
return 0;
}
-/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
+/*! Find matching y in yp:s children, return "yang order" of y or -1 if not found
* @param[in] yp Parent
* @param[in] y Yang datanode to find
* @param[out] index Index of y in yp:s list of children
@@ -815,7 +841,7 @@ order1(yang_stmt *yp,
for (i=0; iys_len; i++){
ys = yp->ys_stmt[i];
if (ys->ys_keyword == Y_CHOICE){
- if (order1_choice(ys, y) == 1) /* If one of the choices is "y" */
+ if (order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */
return 1;
}
else {
@@ -823,8 +849,8 @@ order1(yang_stmt *yp,
continue;
if (ys==y)
return 1;
+ (*index)++;
}
- (*index)++;
}
return 0;
}
@@ -848,7 +874,7 @@ yang_order(yang_stmt *y)
if (y == NULL)
return -1;
- /* Some special handling if yp is choice (or case) and maybe union?
+ /* Some special handling if yp is choice (or case)
* if so, the real parent (from an xml point of view) is the parents
* parent.
*/
@@ -1213,7 +1239,6 @@ yang_print_cbuf(cbuf *cb,
return 0;
}
-
/*! Populate yang leafs after parsing. Create cv and fill it in.
*
* Populate leaf in 2nd round of yang parsing, now that context is complete:
@@ -1308,7 +1333,8 @@ ys_populate_list(yang_stmt *ys,
if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL)
return 0;
- cvec_free(ys->ys_cvec);
+ if (ys->ys_cvec)
+ cvec_free(ys->ys_cvec);
if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL)
return -1;
return 0;
@@ -1681,6 +1707,18 @@ ys_populate_feature(clicon_handle h,
return retval;
}
+/*! Populate the unique statement with a cvec
+ */
+static int
+ys_populate_unique(yang_stmt *ys)
+{
+ if (ys->ys_cvec)
+ cvec_free(ys->ys_cvec);
+ if ((ys->ys_cvec = yang_arg2cvec(ys, " ")) == NULL)
+ return -1;
+ return 0;
+}
+
/*! Populate unknown node with extension
*/
static int
@@ -1770,6 +1808,10 @@ ys_populate(yang_stmt *ys,
if (ys_populate_identity(ys, NULL) < 0)
goto done;
break;
+ case Y_UNIQUE:
+ if (ys_populate_unique(ys) < 0)
+ goto done;
+ break;
case Y_UNKNOWN:
if (ys_populate_unknown(ys) < 0)
goto done;
@@ -3202,7 +3244,9 @@ ys_parse(yang_stmt *ys,
*
* The cv:s created in parse-tree as follows:
* fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass)
- *
+ * revision: cv as uint32 date: Integer version as YYYYMMDD
+ * min-elements: cv as uint32
+ * max-elements: cv as uint32, '0' means unbounded
* @see ys_populate
*/
int
@@ -3212,8 +3256,15 @@ ys_parse_sub(yang_stmt *ys,
int retval = -1;
uint8_t fd;
uint32_t date = 0;
-
- switch (ys->ys_keyword){
+ char *arg;
+ enum rfc_6020 keyword;
+ char *reason = NULL;
+ int ret;
+ uint32_t minmax;
+
+ arg = yang_argument_get(ys);
+ keyword = yang_keyword_get(ys);
+ switch (keyword){
case Y_FRACTION_DIGITS:
if (ys_parse(ys, CGV_UINT8) == NULL)
goto done;
@@ -3225,7 +3276,7 @@ ys_parse_sub(yang_stmt *ys,
break;
case Y_REVISION:
case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */
- if (ys_parse_date_arg(ys->ys_argument, &date) < 0)
+ if (ys_parse_date_arg(arg, &date) < 0)
goto done;
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
@@ -3234,14 +3285,37 @@ ys_parse_sub(yang_stmt *ys,
cv_uint32_set(ys->ys_cv, date);
break;
case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */
- if (strcmp(ys->ys_argument, "current") &&
- strcmp(ys->ys_argument, "deprecated") &&
- strcmp(ys->ys_argument, "obsolete")){
- clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", ys->ys_argument);
+ if (strcmp(arg, "current") &&
+ strcmp(arg, "deprecated") &&
+ strcmp(arg, "obsolete")){
+ clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", arg);
goto done;
}
break;
+ case Y_MAX_ELEMENTS:
+ case Y_MIN_ELEMENTS:
+ if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
+ clicon_err(OE_YANG, errno, "cv_new");
+ goto done;
+ }
+ if (keyword == Y_MAX_ELEMENTS &&
+ strcmp(arg, "unbounded") == 0)
+ cv_uint32_set(ys->ys_cv, 0); /* 0 means unbounded for max */
+ else{
+ if ((ret = parse_uint32(arg, &minmax, &reason)) < 0){
+ clicon_err(OE_YANG, errno, "parse_uint32");
+ goto done;
+ }
+ if (ret == 0){
+ clicon_err(OE_YANG, EINVAL, "element-min/max parse error: %s", reason);
+ if (reason)
+ free(reason);
+ goto done;
+ }
+ cv_uint32_set(ys->ys_cv, minmax);
+ }
+ break;
case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
but it may not be */
if (extra == NULL)
@@ -3273,8 +3347,7 @@ int
yang_mandatory(yang_stmt *ys)
{
yang_stmt *ym;
- char *reason = NULL;
- uint8_t min_elements; /* XXX change to 32 (need new cligen version) */
+ cg_var *cv;
/* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true". */
@@ -3289,12 +3362,8 @@ yang_mandatory(yang_stmt *ys)
* value greater than zero. */
else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){
if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
- /* XXX change to 32 (need new cligen version) */
- if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){
- clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8");
- return 0; /* XXX ignore error */
- }
- return min_elements > 0;
+ cv = yang_cv_get(ym);
+ return cv_uint32_get(cv) > 0;
}
}
/* 3) A container node without a "presence" statement and that has at
diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c
index 96593246..d9303061 100644
--- a/lib/src/clixon_yang_module.c
+++ b/lib/src/clixon_yang_module.c
@@ -255,8 +255,8 @@ yms_build(clicon_handle h,
* @param[in] brief Just name, revision and uri (no cache)
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
- * @retval 0 OK
- * @retval 1 Statedata callback failed
+ * @retval 0 Statedata callback failed
+ * @retval 1 OK
* @notes NYI: schema, deviation
x +--ro modules-state
x +--ro module-set-id string
@@ -288,6 +288,7 @@ yang_modules_state_get(clicon_handle h,
char *msid; /* modules-set-id */
cxobj *x1;
cbuf *cb = NULL;
+ int ret;
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if ((x = clicon_modst_cache_get(h, brief)) != NULL){
@@ -313,8 +314,7 @@ yang_modules_state_get(clicon_handle h,
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
- retval = 1;
- goto done;
+ goto fail;
}
if (xml_rootchild(x, 0, &x) < 0)
goto done;
@@ -326,10 +326,12 @@ yang_modules_state_get(clicon_handle h,
/* Wrap x (again) with new top-level node "top" which merge wants */
if ((x = xml_wrap(x, "top")) < 0)
goto done;
- if (netconf_trymerge(x, yspec, xret) < 0)
+ if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
+ if (ret == 0)
+ goto fail;
}
- retval = 0;
+ retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (cb)
@@ -337,6 +339,9 @@ yang_modules_state_get(clicon_handle h,
if (x)
xml_free(x);
return retval;
+ fail:
+ retval = 0;
+ goto done;
}
/*! For single module state with namespace, get revisions and send upgrade callbacks
diff --git a/test/lib.sh b/test/lib.sh
index f2a9d32c..b53fb45e 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -72,8 +72,8 @@ testname=
# eg logging to a file: RCLOG="-l f/www-data/restconf.log"
: ${RCLOG:=}
-# Wait after daemons (backend/restconf) start. Set to 10 if valgrind
-: ${RCWAIT:=1}
+# Wait after daemons (backend/restconf) start. See mem.sh for valgrind
+: ${RCWAIT:=2}
# Parse yangmodels from https://github.com/YangModels/yang
# Recommended: checkout yangmodels elsewhere in the tree and set the env
@@ -168,6 +168,21 @@ stop_backend(){
fi
}
+# Wait for restconf to stop sending 502 Bad Gateway
+wait_backend(){
+ reply=$(echo ' ]]>]]>' | clixon_netconf -qef $cfg 2> /dev/null)
+ let i=0;
+ while [[ $reply != " ]]>]]>' | clixon_netconf -qef $cfg 2> /dev/null)
+ let i++;
+# echo "wait_backend $i"
+ if [ $i -ge $RCWAIT ]; then
+ err "backend timeout $RCWAIT seconds"
+ fi
+ done
+}
+
start_restconf(){
# Start in background
sudo su -c "$clixon_restconf $RCLOG -D $DBG $*" -s /bin/sh www-data &
@@ -184,6 +199,21 @@ stop_restconf(){
fi
}
+# Wait for restconf to stop sending 502 Bad Gateway
+wait_restconf(){
+ hdr=$(curl --head -sS http://localhost/restconf)
+ let i=0;
+ while [[ $hdr == "HTTP/1.1 502 Bad Gateway"* ]]; do
+ sleep 1
+ hdr=$(curl --head -sS http://localhost/restconf)
+ let i++;
+# echo "wait_restconf $i"
+ if [ $i -ge $RCWAIT ]; then
+ err "restconf timeout $RCWAIT seconds"
+ fi
+ done
+}
+
# Increment test number and print a nice string
new(){
if [ $valgrindtest -eq 1 ]; then
diff --git a/test/mem.sh b/test/mem.sh
index 69d4981b..a35193d3 100755
--- a/test/mem.sh
+++ b/test/mem.sh
@@ -33,7 +33,7 @@ memonce(){
valgrindtest=3 # This means backend valgrind test
sudo chmod 660 $valgrindfile
sudo chown www-data $valgrindfile
- : ${RCWAIT:=5} # valgrind backend needs some time to get up
+ : ${RCWAIT:=10} # valgrind backend needs some time to get up
clixon_restconf="/usr/bin/valgrind --leak-check=full --show-leak-kinds=all --suppressions=./valgrind-clixon.supp --track-fds=yes --trace-children=no --child-silent-after-fork=yes --log-file=$valgrindfile /www-data/clixon_restconf"
;;
diff --git a/test/test_choice.sh b/test/test_choice.sh
index 6e08feb9..145f0dac 100755
--- a/test/test_choice.sh
+++ b/test/test_choice.sh
@@ -20,7 +20,7 @@ cat < $cfg
$dir
/usr/local/share/clixon
$IETFRFC
- system
+ $fyang
/usr/local/lib/$APPNAME/clispec
/usr/local/lib/$APPNAME/cli
$APPNAME
@@ -83,11 +83,27 @@ module system{
}
}
}
+ /* Choice with multiple items */
+ choice mch {
+ case first{
+ leaf-list ma {
+ type int32;
+ }
+ leaf mb {
+ type int32;
+ }
+ }
+ case second{
+ leaf-list mc {
+ type int32;
+ }
+ }
+ }
}
}
EOF
-new "test params: -f $cfg -y $fyang"
+new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
@@ -97,55 +113,55 @@ if [ $BE -ne 0 ]; then
fi
sudo pkill clixon_backend # to be sure
- new "start backend -s init -f $cfg -y $fyang"
- start_backend -s init -f $cfg -y $fyang
+ new "start backend -s init -f $cfg"
+ start_backend -s init -f $cfg
fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
-start_restconf -f $cfg -y $fyang
+start_restconf -f $cfg
new "waiting"
sleep $RCWAIT
# First vanilla (protocol) case
new "netconf validate empty"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf set protocol both udp and tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf validate both udp and tcp fail"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^application bad-element udp error Element in choice statement already exists ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^application bad-element udp error Element in choice statement already exists ]]>]]>$"
new "netconf discard-changes"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf set empty protocol"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf validate protocol"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf set protocol tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf get protocol tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^ ]]>]]>$'
new "netconf commit protocol tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' '^ ]]>]]>$'
new "netconf get protocol udp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^ ]]>]]>$'
new "netconf commit protocol udp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
#-- restconf
@@ -166,66 +182,100 @@ new "restconf set protocol tcp+udp fail"
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d '{"system:protocol":{"tcp": null, "udp": null}}')" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "bad-element","error-info": {"bad-element": "udp"},"error-severity": "error","error-message": "Element in choice statement already exists"}}}
'
new "cli set protocol udp"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol udp" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"
new "cli get protocol udp"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol udp$"
+expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol udp$"
new "cli change protocol to tcp"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol tcp" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -l o set system protocol tcp" 0 "^$"
new "cli get protocol tcp"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol tcp$"
+expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol tcp$"
new "cli delete all"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o delete all" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$"
new "commit"
-expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o commit" 0 "^$"
+expectfn "$clixon_cli -1 -f $cfg -l o commit" 0 "^$"
# Second shorthand (no case)
new "netconf set protocol both udp and tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf validate both udp and tcp fail"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^application bad-element udp error Element in choice statement already exists ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^application bad-element udp error Element in choice statement already exists ]]>]]>$"
new "netconf discard-changes"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf set shorthand tcp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' '^ ]]>]]>$'
new "netconf get shorthand udp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^ ]]>]]>$'
new "netconf validate shorthand"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
new "netconf discard-changes"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
# Third check mandatory
new "netconf set empty mandatory"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf get mandatory empty"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^ ]]>]]>$'
new "netconf validate mandatory"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^application data-missing missing-choice name error ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^application data-missing missing-choice name error ]]>]]>$'
new "netconf set mandatory udp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 ' ]]>]]>' "^ ]]>]]>$"
new "netconf get mandatory udp"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" '^ ]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^ ]]>]]>$'
new "netconf validate mandatory"
-expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " ]]>]]>" "^ ]]>]]>$"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+# Choice with multiple items
+
+new "netconf choice multiple items first"
+expecteof "$clixon_netconf -qf $cfg" 0 '
+ 0
+ 1
+ 2
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate multiple ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "netconf choice multiple items second"
+expecteof "$clixon_netconf -qf $cfg" 0 '
+ 0
+ 1
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate multiple ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "netconf choice multiple items mix"
+expecteof "$clixon_netconf -qf $cfg" 0 '
+ 0
+ 2
+ 2
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate multiple error"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^application bad-element mc error Element in choice statement already exists ]]>]]>$'
new "Kill restconf daemon"
stop_restconf
diff --git a/test/test_list.sh b/test/test_list.sh
deleted file mode 100755
index 7709c074..00000000
--- a/test/test_list.sh
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/bash
-# Yang list / leaf-list operations. min/max-elements
-
-# Magic line must be first in script (see README.md)
-s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
-
-APPNAME=example
-
-cfg=$dir/conf_yang.xml
-fyang=$dir/test.yang
-
-cat < $cfg
-
- $cfg
- /usr/local/share/clixon
- $IETFRFC
- $fyang
- /usr/local/lib/$APPNAME/clispec
- /usr/local/lib/$APPNAME/cli
- $APPNAME
- /usr/local/var/$APPNAME/$APPNAME.sock
- /usr/local/var/$APPNAME/$APPNAME.pidfile
- 1
- /usr/local/var/$APPNAME
-
-EOF
-
-cat < $fyang
-module $APPNAME{
- yang-version 1.1;
- namespace "urn:example:clixon";
- prefix ex;
- container c{
- presence true;
- list a0{
- key k;
- leaf k {
- type string;
- }
- min-elements 0;
- max-elements "unbounded";
- }
- list a1{
- key k;
- leaf k {
- type string;
- }
- description "There must be at least one of these";
- min-elements 1;
- max-elements 2;
- }
- leaf-list b0{
- type string;
- min-elements 0;
- max-elements "unbounded";
- }
- leaf-list b1{
- description "There must be at least one of these";
- type string;
- min-elements 1;
- max-elements 2;
- }
- }
-}
-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"
- # start new backend
- start_backend -s init -f $cfg
-
- new "waiting"
- sleep $RCWAIT
-fi
-
-new "minmax: minimal"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 0 ]]>]]>' "^ ]]>]]>$"
-
-new "minmax: minimal validate ok"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: maximal"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 1 unbounded 0 1 0 1 unbounded 0 1 ]]>]]>' '^ ]]>]]>$'
-
-new "minmax: validate ok"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: empty"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace ]]>]]>' '^ ]]>]]>$'
-
-# NYI
-if false; then # nyi
-new "minmax: validate should fail"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: no list"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 ]]>]]>' '^ ]]>]]>$'
-
-new "minmax: validate should fail"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: no leaf-list"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 ]]>]]>' '^ ]]>]]>$'
-
-new "minmax: validate should fail"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: Too large list"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 1 2 0 ]]>]]>' '^ ]]>]]>$'
-
-new "minmax: validate should fail"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-
-new "minmax: Too large leaf-list"
-expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 0 1 2 ]]>]]>' '^ ]]>]]>$'
-
-new "minmax: validate should fail"
-expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
-fi # NYI
-
-if [ $BE -eq 0 ]; then
- exit # BE
-fi
-
-new "Kill backend"
-# Check if premature kill
-pid=`pgrep -u root -f clixon_backend`
-if [ -z "$pid" ]; then
- err "backend already dead"
-fi
-# kill backend
-stop_backend -f $cfg
-
-rm -rf $dir
diff --git a/test/test_minmax.sh b/test/test_minmax.sh
new file mode 100755
index 00000000..5c7faf5d
--- /dev/null
+++ b/test/test_minmax.sh
@@ -0,0 +1,241 @@
+#!/bin/bash
+# Yang list / leaf-list min/max-element tests.
+
+# Magic line must be first in script (see README.md)
+s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
+
+APPNAME=example
+
+cfg=$dir/conf_yang.xml
+fyang=$dir/test.yang
+
+cat < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ $IETFRFC
+ $fyang
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ 1
+ /usr/local/var/$APPNAME
+
+EOF
+
+cat < $fyang
+module $APPNAME{
+ yang-version 1.1;
+ namespace "urn:example:clixon";
+ prefix ex;
+ container c{
+ presence true;
+ list a0{
+ key k;
+ leaf k {
+ type string;
+ }
+ min-elements 0;
+ max-elements "unbounded";
+ }
+ list a1{
+ key k;
+ leaf k {
+ type string;
+ }
+ description "There must be at least one of these";
+ min-elements 1;
+ max-elements 2;
+ }
+ leaf-list b0{
+ type string;
+ min-elements 0;
+ max-elements "unbounded";
+ }
+ leaf-list b1{
+ description "There must be at least one of these";
+ type string;
+ min-elements 1;
+ max-elements 2;
+ }
+ }
+ container c2{
+ description "with choices";
+ choice ch1{
+ case x{
+ list a1{
+ key k;
+ leaf k {
+ type string;
+ }
+ min-elements 1;
+ max-elements 2;
+ }
+ }
+ case y{
+ list a2{
+ key k;
+ leaf k {
+ type string;
+ }
+ min-elements 0;
+ max-elements 3;
+ }
+ }
+ }
+ choice ch2{
+ leaf-list b1{
+ type string;
+ min-elements 1;
+ max-elements 2;
+ }
+ }
+ }
+ leaf-list b3{
+ description "top-level";
+ type int32;
+ min-elements 1;
+ max-elements 2;
+ }
+}
+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"
+ # start new backend
+ start_backend -s init -f $cfg
+
+ new "waiting"
+ sleep $RCWAIT
+fi
+
+new "minmax: minimal"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 0
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax: minimal validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "minmax: maximal"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 1 unbounded 0 1 0 1 unbounded 0 1 ]]>]]>' '^ ]]>]]>$'
+
+new "minmax: validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "minmax: empty"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace ]]>]]>' '^ ]]>]]>$'
+
+#XXX echo ' ]]>]]>' |$clixon_netconf -qf $cfg
+new "minmax: validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-few-elements error c ]]>]]>$'
+
+new "minmax: no list"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 ]]>]]>' '^ ]]>]]>$'
+
+new "minmax: validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-few-elements error c ]]>]]>$'
+
+new "minmax: no leaf-list"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 ]]>]]>' '^ ]]>]]>$'
+
+new "minmax: validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-few-elements error c ]]>]]>$'
+
+new "minmax: Too large list"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 1 2 0 ]]>]]>' '^ ]]>]]>$'
+
+new "minmax: validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-many-elements error a1 ]]>]]>$'
+
+new "minmax: Too large leaf-list"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace 0 0 1 2 ]]>]]>' '^ ]]>]]>$'
+
+new "minmax: validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-many-elements error b1 ]]>]]>$'
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+# With choice c1
+new "minmax choice: minimal"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 0
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax choice: minimal validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "minmax choice: many a2"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 1
+ 2
+ 0
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax choice: many a2 validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "minmax choice: too many a1"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 1
+ 2
+ 0
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax choice: too many validate should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-many-elements error a1 ]]>]]>$'
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+# top config level
+new "minmax top level"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 1
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax top level ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "minmax top level too many"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ 0
+ 1
+ 2
+ ]]>]]>' "^ ]]>]]>$"
+
+new "minmax top level too many should fail"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed too-many-elements error b3 ]]>]]>'
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+if [ $BE -eq 0 ]; then
+ exit # BE
+fi
+
+new "Kill backend"
+# Check if premature kill
+pid=`pgrep -u root -f clixon_backend`
+if [ -z "$pid" ]; then
+ err "backend already dead"
+fi
+# kill backend
+stop_backend -f $cfg
+
+rm -rf $dir
diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh
index 15b16fcd..6645ef11 100755
--- a/test/test_nacm_default.sh
+++ b/test/test_nacm_default.sh
@@ -106,8 +106,10 @@ EOF
start_restconf -f $cfg -- -a
new "waiting"
- sleep $RCWAIT
+ wait_backend
+ wait_restconf
+
#----------- First get
case "$ret1" in
0) ret='{"nacm-example:x": 42}
@@ -119,6 +121,7 @@ EOF
'
;;
esac
+
new "get startup 42"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret"
@@ -194,5 +197,4 @@ testrun true permit deny permit 0 1 3
new "nacm enabled, exec default permit, write permit (expect fail)"
testrun true deny permit permit 2 0 2
-
rm -rf $dir
diff --git a/test/test_order.sh b/test/test_order.sh
index 0408d03e..375c537c 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -221,6 +221,24 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' '^c bar b foo a fie ]]>]]>$'
+new "Overwrite existing ordered-by user y2->c"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '
+c newc
+ ]]>]]>'
+
+new "Overwrite existing ordered-by user y2->b"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '
+b newb
+ ]]>]]>'
+
+new "Overwrite existing ordered-by user y2->a"
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '
+a newa
+ ]]>]]>'
+
+new "Tests for no duplicates."
+expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ' ]]>]]>' '^c newc b newb a newa ]]>]]>$'
+
#-- order by type rather than strings.
# there are three leaf-lists:strings, ints, and decimal64, and two lists:
# listints and listdecs
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index be4f73b0..a2e9ff59 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -53,7 +53,8 @@ new "start restconf daemon"
start_restconf -f $cfg
new "waiting"
-sleep $RCWAIT
+wait_backend
+wait_restconf
new "restconf tests"
@@ -73,12 +74,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
# Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
-expecteq "$(curl -sG http://localhost/restconf/operations)" 0 '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null,"ietf-netconf:get-config": null,"ietf-netconf:edit-config": null,"ietf-netconf:copy-config": null,"ietf-netconf:delete-config": null,"ietf-netconf:lock": null,"ietf-netconf:unlock": null,"ietf-netconf:get": null,"ietf-netconf:close-session": null,"ietf-netconf:kill-session": null,"ietf-netconf:commit": null,"ietf-netconf:discard-changes": null,"ietf-netconf:validate": null,"clixon-rfc5277:create-subscription": null}}
+expecteq "$(curl -sG http://localhost/restconf/operations)" 0 '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null,"clixon-lib:ping": null,"ietf-netconf:get-config": null,"ietf-netconf:edit-config": null,"ietf-netconf:copy-config": null,"ietf-netconf:delete-config": null,"ietf-netconf:lock": null,"ietf-netconf:unlock": null,"ietf-netconf:get": null,"ietf-netconf:close-session": null,"ietf-netconf:kill-session": null,"ietf-netconf:commit": null,"ietf-netconf:discard-changes": null,"ietf-netconf:validate": null,"clixon-rfc5277:create-subscription": 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"
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index 0ec36b11..52b6d2a5 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -85,7 +85,8 @@ new "start restconf daemon"
start_restconf -f $cfg -y $fyang
new "waiting"
-sleep $RCWAIT
+wait_backend
+wait_restconf
new "restconf tests"
diff --git a/test/test_stream.sh b/test/test_stream.sh
index bf17203f..ea8fc03c 100755
--- a/test/test_stream.sh
+++ b/test/test_stream.sh
@@ -25,7 +25,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
: ${clixon_util_stream:=clixon_util_stream}
-NCWAIT=5 # Wait (netconf valgrind may need more time)
+NCWAIT=10 # Wait (netconf valgrind may need more time)
DATE=$(date +"%Y-%m-%d")
@@ -120,7 +120,8 @@ new "start restconf daemon"
start_restconf -f $cfg -y $fyang
new "waiting"
-sleep $RCWAIT
+wait_backend
+wait_restconf
#
# 1. Netconf RFC5277 stream testing
diff --git a/test/test_unique.sh b/test/test_unique.sh
new file mode 100755
index 00000000..799a51bb
--- /dev/null
+++ b/test/test_unique.sh
@@ -0,0 +1,226 @@
+#!/bin/bash
+# Yang list unique tests
+# Use example in RFC7890 7.8.3.1, modify fields and also test a variant with
+# a single unique identifier (rfc example has two)
+# The test adds the rfc conf that fails, then one that passes, then makes add
+# to fail it and then del to pass it.
+# Then makes a fail / pass test on the single field case
+# Last, a complex unsorted list with several sub-elements.
+
+# Magic line must be first in script (see README.md)
+s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
+
+APPNAME=example
+
+cfg=$dir/conf_yang.xml
+fyang=$dir/unique.yang
+
+cat < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ $fyang
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ 1
+ /usr/local/var/$APPNAME
+
+EOF
+
+# Example (the list server part) from RFC7950 Sec 7.8.3.1 w changed types
+cat < $fyang
+module unique{
+ yang-version 1.1;
+ namespace "urn:example:clixon";
+ prefix un;
+ container c{
+ leaf a{
+ type string;
+ }
+ list server {
+ description "RFC7950 7.8.3.1";
+ key "name";
+ unique "ip port";
+ leaf name {
+ type string;
+ }
+ leaf ip {
+ type string;
+ }
+ leaf port {
+ type uint16;
+ }
+ }
+ list other {
+ description "random inserted data";
+ key a;
+ leaf a{
+ type string;
+ }
+ }
+ list single {
+ description "similar with just a single unique field";
+ key "name";
+ unique "ip";
+ leaf name {
+ type string;
+ }
+ leaf ip {
+ type string;
+ }
+ }
+ leaf b{
+ type string;
+ }
+ }
+}
+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"
+ # start new backend
+ start_backend -s init -f $cfg
+
+ new "waiting"
+ sleep $RCWAIT
+fi
+
+# RFC test two-field caes
+new "Add not valid example"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ smtp
+ 192.0.2.1
+ 25
+
+
+ http
+ 192.0.2.1
+ 25
+
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate (should fail)"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed data-not-unique error 192.0.2.1 25 ]]>]]>$'
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "Add valid example"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ smtp
+ 192.0.2.1
+ 25
+
+
+ http
+ 192.0.2.1
+
+
+ ftp
+ 192.0.2.1
+
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "make it invalid by adding port to ftp entry"
+expecteof "$clixon_netconf -qf $cfg" 0 'none ftp 25
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate (should fail)"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed data-not-unique error 192.0.2.1 25 ]]>]]>$'
+
+new "make it valid by deleting port from smtp entry"
+expecteof "$clixon_netconf -qf $cfg" 0 'none smtp 25
+ ]]>]]>' '^ ]]>]]>$'
+
+new "netconf validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+# Then test single-field case
+new "Add not valid example"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ smtp
+ 192.0.2.1
+
+
+ http
+ 192.0.2.1
+
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate (should fail)"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed data-not-unique error 192.0.2.1 ]]>]]>$'
+
+new "make valid by replacing IP of http entry"
+expecteof "$clixon_netconf -qf $cfg" 0 'none http 178.23.34.1
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate ok"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+# Then test composite case (detect duplicates among other elements)
+# and also unordered
+
+new "Add not valid example"
+expecteof "$clixon_netconf -qf $cfg" 0 'replace
+ other
+
+ smtp
+ 192.0.2.1
+
+ other
+
+ smtp
+ 192.0.2.1
+ 25
+
+
+ http
+ 192.0.2.1
+
+ xx
+
+ http
+ 192.0.2.1
+ 25
+
+ ]]>]]>' "^ ]]>]]>$"
+
+new "netconf validate (should fail)"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" '^protocol operation-failed data-not-unique error 192.0.2.1 25 ]]>]]>$'
+
+new "netconf discard-changes"
+expecteof "$clixon_netconf -qf $cfg" 0 " ]]>]]>" "^ ]]>]]>$"
+
+if [ $BE -eq 0 ]; then
+ exit # BE
+fi
+
+new "Kill backend"
+# Check if premature kill
+pid=`pgrep -u root -f clixon_backend`
+if [ -z "$pid" ]; then
+ err "backend already dead"
+fi
+# kill backend
+stop_backend -f $cfg
+
+rm -rf $dir
diff --git a/yang/clixon/clixon-lib@2019-01-02.yang b/yang/clixon/clixon-lib@2019-01-02.yang
index 1e26187b..e0e3b03d 100644
--- a/yang/clixon/clixon-lib@2019-01-02.yang
+++ b/yang/clixon/clixon-lib@2019-01-02.yang
@@ -52,4 +52,7 @@ module clixon-lib {
}
}
}
+ rpc ping {
+ description "Check aliveness of backend daemon.";
+ }
}