This commit is contained in:
Olof hagsand 2019-05-08 13:05:51 +02:00
commit 70221742f7
26 changed files with 1314 additions and 314 deletions

View file

@ -3,6 +3,15 @@
## 3.10.0/4.0.0 (Upcoming) ## 3.10.0/4.0.0 (Upcoming)
### Major New features ### 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) * 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 design is similar to bash history:
* The CLI loads/saves its complete history to a file on entry and exit, respectively * 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) ### 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) * 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 `<rpc xmlns="http://clicon.org/lib"><ping/></rpc>]]>]]>`
* Restconf with startup feature will now copy all edit changes to startup db (as it should according to RFC 8040) * 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) * Netconf Startup feature is no longer hardcoded, you need to explicitly enable it (See RFC 6241, Section 8.7)
* Enable in config file with: `<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>`, or use `*:*` * Enable in config file with: `<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>`, or use `*:*`
@ -116,6 +130,8 @@
### Minor changes ### Minor changes
* New XMLDB_FORMAT added: `tree`. An experimental record-based tree database for direct access of records. * 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 * A new minimal "hello world" example has been added
* Experimental customized error output strings, see [lib/clixon/clixon_err_string.h] * Experimental customized error output strings, see [lib/clixon/clixon_err_string.h]
* Empty leaf values, eg <a></a> are now checked at validation. * Empty leaf values, eg <a></a> are now checked at validation.
@ -143,6 +159,9 @@
* Added libgen.h for baseline() * Added libgen.h for baseline()
### Corrected Bugs ### 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) * [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. * 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. * 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.

View file

@ -111,13 +111,11 @@ Clixon follows:
However, the following YANG syntax modules are not implemented: However, the following YANG syntax modules are not implemented:
- deviation - deviation
- min/max-elements
- unique
- action - action
- refine - 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) - 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) - Default values on leaf-lists are not supported (RFC7950 7.7.2)

View file

@ -158,8 +158,8 @@ backend_client_rm(clicon_handle h,
* @param[in] top Top symbol, ie netconf or restconf-state * @param[in] top Top symbol, ie netconf or restconf-state
* @param[in,out] xret Existing XML tree, merge x into this * @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal) * @retval -1 Error (fatal)
* @retval 0 OK * @retval 0 Statedata callback failed
* @retval 1 Statedata callback failed * @retval 1 OK
*/ */
static int static int
client_get_streams(clicon_handle h, client_get_streams(clicon_handle h,
@ -174,6 +174,7 @@ client_get_streams(clicon_handle h,
yang_stmt *yns = NULL; /* yang namespace */ yang_stmt *yns = NULL; /* yang namespace */
cxobj *x = NULL; cxobj *x = NULL;
cbuf *cb = NULL; cbuf *cb = NULL;
int ret;
if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){ if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s yang module not found", module); 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 (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done; goto done;
retval = 1; goto fail;
goto done;
} }
retval = netconf_trymerge(x, yspec, xret); if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
retval = 1;
done: done:
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (x) if (x)
xml_free(x); xml_free(x);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Get system state-data, including streams and plugins /*! 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] xpath Xpath selection, not used but may be to filter early
* @param[in,out] xret Existing XML tree, merge x into this * @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal) * @retval -1 Error (fatal)
* @retval 0 OK * @retval 0 Statedata callback failed (clicon_err called)
* @retval 1 Statedata callback failed (clicon_err called) * @retval 1 OK
*/ */
static int static int
client_statedata(clicon_handle h, client_statedata(clicon_handle h,
@ -225,22 +232,34 @@ client_statedata(clicon_handle h,
size_t xlen; size_t xlen;
int i; int i;
yang_stmt *yspec; yang_stmt *yspec;
int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
} }
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
if ((retval = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) != 0) if ((ret = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) < 0)
goto done; goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")) if (ret == 0)
if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 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; goto done;
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")) if (ret == 0)
if ((retval = yang_modules_state_get(h, yspec, xpath, 0, xret)) != 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; 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; goto done;
if (ret == 0)
goto fail;
/* Code complex to filter out anything that is outside of xpath */ /* Code complex to filter out anything that is outside of xpath */
if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done; goto done;
@ -259,12 +278,15 @@ client_statedata(clicon_handle h,
/* reset flag */ /* reset flag */
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done; goto done;
retval = 0; /* OK */ retval = 1; /* OK */
done: done:
clicon_debug(1, "%s %d", __FUNCTION__, retval); clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (xvec) if (xvec)
free(xvec); free(xvec);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Retrieve all or part of a specified configuration. /*! Retrieve all or part of a specified configuration.
@ -789,7 +811,7 @@ from_client_get(clicon_handle h,
clicon_err_reset(); clicon_err_reset();
if ((ret = client_statedata(h, xpath, &xret)) < 0) if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done; 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) if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done; goto done;
goto ok; goto ok;
@ -1045,6 +1067,26 @@ from_client_debug(clicon_handle h,
return retval; return retval;
} }
/*! Check liveness of backend daemon, just send a reply
* @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*/
static int
from_client_ping(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
/*! An internal clicon message has arrived from a client. Receive and dispatch. /*! An internal clicon message has arrived from a client. Receive and dispatch.
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] s Socket where message arrived. read from this. * @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, if (rpc_callback_register(h, from_client_create_subscription, NULL,
"urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0) "urn:ietf:params:xml:ns:netmod:notification", "create-subscription") < 0)
goto done; goto done;
/* In backend_client.? Clixon RPC */ /* Clixon RPC */
if (rpc_callback_register(h, from_client_debug, NULL, if (rpc_callback_register(h, from_client_debug, NULL,
"http://clicon.org/lib", "debug") < 0) "http://clicon.org/lib", "debug") < 0)
goto done; goto done;
if (rpc_callback_register(h, from_client_ping, NULL,
"http://clicon.org/lib", "ping") < 0)
goto done;
retval =0; retval =0;
done: done:
return retval; return retval;

View file

@ -115,8 +115,8 @@ clixon_plugin_reset(clicon_handle h,
* @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xtop State XML tree is merged with existing tree. * @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error * @retval -1 Error
* @retval 0 OK * @retval 0 Statedata callback failed (xret set with netconf-error)
* @retval 1 Statedata callback failed (xret set with netconf-error) * @retval 1 OK
* @note xtop can be replaced * @note xtop can be replaced
*/ */
int int
@ -136,24 +136,25 @@ clixon_plugin_statedata(clicon_handle h,
continue; continue;
if ((x = xml_new("config", NULL, NULL)) == NULL) if ((x = xml_new("config", NULL, NULL)) == NULL)
goto done; goto done;
if (fn(h, xpath, x) < 0){ if (fn(h, xpath, x) < 0)
retval = 1; goto fail; /* Dont quit here on user callbacks */
goto done; /* Dont quit here on user callbacks */ if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
}
if ((ret = netconf_trymerge(x, yspec, xret)) != 0){
retval = ret;
goto done; goto done;
} if (ret == 0)
goto fail;
if (x){ if (x){
xml_free(x); xml_free(x);
x = NULL; x = NULL;
} }
} }
retval = 0; retval = 1;
done: done:
if (x) if (x)
xml_free(x); xml_free(x);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Create and initialize transaction */ /*! Create and initialize transaction */

View file

@ -331,6 +331,8 @@ startup_failsafe(clicon_handle h)
/*! Init modules state of the backend (server). To compare with startup XML /*! Init modules state of the backend (server). To compare with startup XML
* Set the modules state as setopt to the datastore module. * Set the modules state as setopt to the datastore module.
* Only if CLICON_XMLDB_MODSTATE is enabled * Only if CLICON_XMLDB_MODSTATE is enabled
* @retval -1 Error
* @retval 0 OK
*/ */
int int
startup_module_state(clicon_handle h, startup_module_state(clicon_handle h,
@ -338,17 +340,23 @@ startup_module_state(clicon_handle h,
{ {
int retval = -1; int retval = -1;
cxobj *x = NULL; cxobj *x = NULL;
int ret;
if (!clicon_option_bool(h, "CLICON_XMLDB_MODSTATE")) if (!clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
goto ok; goto ok;
/* Set up cache /* Set up cache
* Now, access brief module cache with clicon_modst_cache_get(h, 1) */ * 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; goto done;
if (ret == 0)
goto fail;
ok: ok:
retval = 0; retval = 1;
done: done:
if (x) if (x)
xml_free(x); xml_free(x);
return retval; return retval;
fail:
retval = 0;
goto done;
} }

View file

@ -71,10 +71,13 @@
#include "netconf_rpc.h" #include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */ /* 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" #define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
/*! Ignore errors on packet errors: continue */
static int ignore_packet_errors = 1;
/*! Process incoming packet /*! Process incoming packet
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] cb Packet buffer * @param[in] cb Packet buffer
@ -235,8 +238,9 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */ /* OK, we have an xml string from a client */
/* Remove trailer */ /* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (netconf_input_packet(h, cb) < 0) if (netconf_input_packet(h, cb) < 0 &&
; //goto done; // ignore errors !ignore_packet_errors) // default is to ignore errors
goto done;
if (cc_closed) if (cc_closed)
break; break;
cbuf_reset(cb); cbuf_reset(cb);
@ -338,6 +342,7 @@ usage(clicon_handle h,
"\t-y <file>\tLoad yang spec file (override yang main module)\n" "\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n" "\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-t <sec>\tTimeout in seconds. Quit after this time.\n" "\t-t <sec>\tTimeout in seconds. Quit after this time.\n"
"\t-e \tDont ignore errors on packet input.\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n", "\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0, argv0,
clicon_netconf_dir(h) clicon_netconf_dir(h)
@ -349,6 +354,7 @@ int
main(int argc, main(int argc,
char **argv) char **argv)
{ {
int retval = -1;
int c; int c;
char *argv0 = argv[0]; char *argv0 = argv[0];
int quiet = 0; int quiet = 0;
@ -455,6 +461,9 @@ main(int argc,
case 't': /* timeout in seconds */ case 't': /* timeout in seconds */
tv.tv_sec = atoi(optarg); tv.tv_sec = atoi(optarg);
break; break;
case 'e': /* dont ignore packet errors */
ignore_packet_errors = 0;
break;
case 'o':{ /* Configuration option */ case 'o':{ /* Configuration option */
char *val; char *val;
if ((val = index(optarg, '=')) == NULL) if ((val = index(optarg, '=')) == NULL)
@ -528,9 +537,12 @@ main(int argc,
} }
if (event_loop() < 0) if (event_loop() < 0)
goto done; goto done;
retval = 0;
done: done:
if (ignore_packet_errors)
retval = 0;
netconf_terminate(h); netconf_terminate(h);
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid()); clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
return 0; return retval;
} }

View file

@ -588,6 +588,10 @@ api_data_post(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){ if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4: /* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST * If the NETCONF server supports :startup, the RESTCONF server MUST
@ -933,6 +937,10 @@ api_data_put(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){ if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4: /* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST * If the NETCONF server supports :startup, the RESTCONF server MUST
@ -1108,6 +1116,10 @@ api_data_delete(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){ if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4: /* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST * If the NETCONF server supports :startup, the RESTCONF server MUST

View file

@ -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_operation_failed_xml(cxobj **xret, char *type, char *message);
int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message(cbuf *cb, char *message);
int netconf_malformed_message_xml(cxobj **xret, 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_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret);
int netconf_module_load(clicon_handle h); int netconf_module_load(clicon_handle h);
char *netconf_db_find(cxobj *xn, char *name); char *netconf_db_find(cxobj *xn, char *name);

View file

@ -966,13 +966,90 @@ netconf_malformed_message_xml(cxobj **xret,
return retval; 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, "<rpc-reply><rpc-error>"
"<error-type>protocol</error-type>"
"<error-tag>operation-failed</error-tag>"
"<error-app-tag>data-not-unique</error-app-tag>"
"<error-severity>error</error-severity>"
"<error-info>") < 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, "<non-unique>");
clicon_xml2cbuf(cb, xi, 0, 0);
cprintf(cb, "</non-unique>");
}
if (cprintf(cb, "</error-info></rpc-error></rpc-reply>") <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, "<rpc-reply><rpc-error>"
"<error-type>protocol</error-type>"
"<error-tag>operation-failed</error-tag>"
"<error-app-tag>too-%s-elements</error-app-tag>"
"<error-severity>error</error-severity>"
"<error-path>%s</error-path>"
"</rpc-error></rpc-reply>",
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 /*! Help function: merge - check yang - if error make netconf errmsg
* @param[in] x XML tree * @param[in] x XML tree
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[in,out] xret Existing XML tree, merge x into this * @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal) * @retval -1 Error (fatal)
* @retval 0 OK * @retval 0 Statedata callback failed
* @retval 1 Statedata callback failed * @retval 1 OK
*/ */
int int
netconf_trymerge(cxobj *x, netconf_trymerge(cxobj *x,
@ -995,15 +1072,17 @@ netconf_trymerge(cxobj *x,
xml_purge(xc); xml_purge(xc);
if (netconf_operation_failed_xml(xret, "rpc", reason)< 0) if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
goto done; goto done;
retval = 1; goto fail;
goto done;
} }
ok: ok:
retval = 0; retval = 1;
done: done:
if (reason) if (reason)
free(reason); free(reason);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Load ietf netconf yang module and set enabled features /*! Load ietf netconf yang module and set enabled features

View file

@ -503,7 +503,6 @@ rpc_callback_call(clicon_handle h,
void *arg) void *arg)
{ {
int retval = -1; int retval = -1;
int ret;
rpc_callback_t *rc; rpc_callback_t *rc;
char *name; char *name;
char *prefix; char *prefix;
@ -520,7 +519,7 @@ rpc_callback_call(clicon_handle h,
if (strcmp(rc->rc_name, name) == 0 && if (strcmp(rc->rc_name, name) == 0 &&
namespace && rc->rc_namespace && namespace && rc->rc_namespace &&
strcmp(rc->rc_namespace, namespace) == 0){ 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); clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
goto done; goto done;
} }

View file

@ -495,6 +495,7 @@ xml_yang_validate_rpc(cxobj *xrpc,
* @retval 1 Validation OK * @retval 1 Validation OK
* @retval 0 Validation failed (cbret set) * @retval 0 Validation failed (cbret set)
* @retval -1 Error * @retval -1 Error
* Check if xt is part of valid choice
*/ */
static int static int
check_choice(cxobj *xt, check_choice(cxobj *xt,
@ -502,27 +503,59 @@ check_choice(cxobj *xt,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
yang_stmt *yc;
yang_stmt *y; 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; yang_stmt *yp;
cxobj *x; cxobj *x;
cxobj *xp; cxobj *xp;
if ((yc = yang_choice(yt)) == NULL) if ((ytp = yang_parent_get(yt)) == NULL)
goto ok; 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) if ((xp = xml_parent(xt)) == NULL)
goto ok; 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) { while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
if ((x != xt) && if (x == xt)
(y = xml_spec(x)) != NULL && continue;
(yp = yang_choice(y)) != NULL && y = xml_spec(x);
yp == yc){ if (y == yt) /* eg same list */
if (netconf_bad_element(cbret, "application", xml_name(x), "Element in choice statement already exists") < 0) continue;
goto done; yp = yang_parent_get(y);
goto fail; 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: ok:
retval = 1; retval = 1;
done: done:
@ -671,6 +704,311 @@ check_list_key(cxobj *xt,
goto done; 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<i1; i++){
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
break;
}
if (v==vlen) /* duplicate */
break;
}
return i==i1?0:-1;
}
/*! Given a list with unique constraint, detect duplicates
* @param[in] x The first element in the list (on return the last)
* @param[in] xt The parent of x
* @param[in] y Its yang spec (Y_LIST)
* @param[in] yu A yang unique spec (Y_UNIQUE)
* @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
* @note It would be possible to cache the vector built below
*/
static int
check_unique_list(cxobj *x,
cxobj *xt,
yang_stmt *y,
yang_stmt *yu,
cbuf *cbret)
{
int retval = -1;
cvec *cvk; /* unique vector */
cg_var *cvi; /* unique node name */
cxobj *xi;
char **vec = NULL; /* 2xmatrix */
int vlen;
int i;
int v;
char *bi;
cvk = yang_cvec_get(yu);
vlen = cvec_len(cvk); /* nr of unique elements to check */
if ((vec = calloc(vlen*xml_child_nr(xt), sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
i = 0; /* x element index */
do {
cvi = NULL;
v = 0; /* index in each tuple */
while ((cvi = cvec_each(cvk, cvi)) != NULL){
/* RFC7950: Sec 7.8.3.1: entries that do not have value for all
* referenced leafs are not taken into account */
if ((xi = xml_find(x, cv_string_get(cvi))) ==NULL)
break;
if ((bi = xml_body(xi)) == NULL)
break;
vec[i*vlen + v++] = bi;
}
if (cvi==NULL){
/* Last element (i) is newly inserted, see if it is already there */
if (check_insert_duplicate(vec, i, vlen) < 0){
if (netconf_data_not_unique(cbret, x, cvk) < 0)
goto done;
goto fail;
}
}
x = xml_child_each(xt, x, CX_ELMNT);
i++;
} while (x && y == xml_spec(x)); /* stop if list ends, others may follow */
/* It would be possible to cache vec here as an optimization */
retval = 1;
done:
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given a list, check if any min/max-elemants constraints apply
* @param[in] x One x (the last) of a specific lis
* @param[in] y Yang spec of x
* @param[in] nr Number of elements (like x) in thlist
* @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
* @see RFC7950 7.7.5
*/
static int
check_min_max(cxobj *x,
yang_stmt *y,
int nr,
cbuf *cbret)
{
int retval = -1;
yang_stmt *ymin; /* yang min */
yang_stmt *ymax; /* yang max */
cg_var *cv;
if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ymin);
if (nr < cv_uint32_get(cv)){
if (netconf_minmax_elements(cbret, x, 0) < 0)
goto done;
goto fail;
}
}
if ((ymax = yang_find(y, Y_MAX_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ymax);
if (cv_uint32_get(cv) > 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 /*! Validate a single XML node with yang specification for added entry
* 1. Check if mandatory leafs present as subs. * 1. Check if mandatory leafs present as subs.
* 2. Check leaf values, eg int ranges and string regexps. * 2. Check leaf values, eg int ranges and string regexps.
@ -850,9 +1188,9 @@ xml_yang_validate_all(cxobj *xt,
goto done; goto done;
goto fail; goto fail;
} }
if (ys != NULL && yang_config(ys) != 0){ if (yang_config(ys) != 0){
/* Node-specific validation */ /* Node-specific validation */
switch (ys->ys_keyword){ switch (yang_keyword_get(ys)){
case Y_ANYXML: case Y_ANYXML:
case Y_ANYDATA: case Y_ANYDATA:
goto ok; goto ok;
@ -872,33 +1210,6 @@ xml_yang_validate_all(cxobj *xt,
if (validate_identityref(xt, ys, yc, cbret) < 0) if (validate_identityref(xt, ys, yc, cbret) < 0)
goto done; 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; break;
default: default:
@ -941,6 +1252,14 @@ xml_yang_validate_all(cxobj *xt,
if (ret == 0) if (ret == 0)
goto fail; 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: ok:
retval = 1; retval = 1;
done: done:
@ -967,6 +1286,8 @@ xml_yang_validate_all_top(cxobj *xt,
if ((ret = xml_yang_validate_all(x, cbret)) < 1) if ((ret = xml_yang_validate_all(x, cbret)) < 1)
return ret; return ret;
} }
if ((ret = check_list_unique_minmax(xt, cbret)) < 1)
return ret;
return 1; return 1;
} }

View file

@ -416,11 +416,9 @@ xml_search1(cxobj *xp,
cmp = yangi-yang_order(y); cmp = yangi-yang_order(y);
/* Here is right yang order == same yang? */ /* Here is right yang order == same yang? */
if (cmp == 0){ 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); return xml_search_userorder(xp, x1, y, yangi, mid);
}
else /* Ordered by system */
cmp = xml_cmp(x1, xc, 0);
} }
if (cmp == 0) if (cmp == 0)
return xc; return xc;

View file

@ -759,12 +759,12 @@ yang_choice(yang_stmt *y)
yang_stmt *yp; yang_stmt *yp;
if ((yp = y->ys_parent) != NULL){ if ((yp = y->ys_parent) != NULL){
switch (yp->ys_keyword){ switch (yang_keyword_get(yp)){
case Y_CHOICE: case Y_CHOICE:
return yp; return yp;
break; break;
case Y_CASE: case Y_CASE:
return yp->ys_parent; return yang_parent_get(yp);
break; break;
default: default:
break; break;
@ -773,31 +773,57 @@ yang_choice(yang_stmt *y)
return NULL; 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 static int
order1_choice(yang_stmt *yp, order1_choice(yang_stmt *yp,
yang_stmt *y) yang_stmt *y,
int *index)
{ {
yang_stmt *ys; yang_stmt *ys;
yang_stmt *yc; yang_stmt *yc;
int i; int i;
int j; int j;
int shortcut=0;
int max=0;
for (i=0; i<yp->ys_len; i++){ for (i=0; i<yp->ys_len; i++){ /* Loop through choice */
ys = yp->ys_stmt[i]; ys = yp->ys_stmt[i];
if (ys->ys_keyword == Y_CASE){ if (ys->ys_keyword == Y_CASE){ /* Loop through case */
for (j=0; j<ys->ys_len; j++){ for (j=0; j<ys->ys_len; j++){
yc = ys->ys_stmt[j]; yc = ys->ys_stmt[j];
if (yang_datanode(yc) && yc == y) if (yang_datanode(yc) && yc == y){
*index += j;
return 1; 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; 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] yp Parent
* @param[in] y Yang datanode to find * @param[in] y Yang datanode to find
* @param[out] index Index of y in yp:s list of children * @param[out] index Index of y in yp:s list of children
@ -815,7 +841,7 @@ order1(yang_stmt *yp,
for (i=0; i<yp->ys_len; i++){ for (i=0; i<yp->ys_len; i++){
ys = yp->ys_stmt[i]; ys = yp->ys_stmt[i];
if (ys->ys_keyword == Y_CHOICE){ 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; return 1;
} }
else { else {
@ -823,8 +849,8 @@ order1(yang_stmt *yp,
continue; continue;
if (ys==y) if (ys==y)
return 1; return 1;
(*index)++;
} }
(*index)++;
} }
return 0; return 0;
} }
@ -848,7 +874,7 @@ yang_order(yang_stmt *y)
if (y == NULL) if (y == NULL)
return -1; 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 * if so, the real parent (from an xml point of view) is the parents
* parent. * parent.
*/ */
@ -1213,7 +1239,6 @@ yang_print_cbuf(cbuf *cb,
return 0; return 0;
} }
/*! Populate yang leafs after parsing. Create cv and fill it in. /*! Populate yang leafs after parsing. Create cv and fill it in.
* *
* Populate leaf in 2nd round of yang parsing, now that context is complete: * 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) if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL)
return 0; return 0;
cvec_free(ys->ys_cvec); if (ys->ys_cvec)
cvec_free(ys->ys_cvec);
if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL) if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL)
return -1; return -1;
return 0; return 0;
@ -1681,6 +1707,18 @@ ys_populate_feature(clicon_handle h,
return retval; 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 /*! Populate unknown node with extension
*/ */
static int static int
@ -1770,6 +1808,10 @@ ys_populate(yang_stmt *ys,
if (ys_populate_identity(ys, NULL) < 0) if (ys_populate_identity(ys, NULL) < 0)
goto done; goto done;
break; break;
case Y_UNIQUE:
if (ys_populate_unique(ys) < 0)
goto done;
break;
case Y_UNKNOWN: case Y_UNKNOWN:
if (ys_populate_unknown(ys) < 0) if (ys_populate_unknown(ys) < 0)
goto done; goto done;
@ -3202,7 +3244,9 @@ ys_parse(yang_stmt *ys,
* *
* The cv:s created in parse-tree as follows: * 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) * 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 * @see ys_populate
*/ */
int int
@ -3212,8 +3256,15 @@ ys_parse_sub(yang_stmt *ys,
int retval = -1; int retval = -1;
uint8_t fd; uint8_t fd;
uint32_t date = 0; uint32_t date = 0;
char *arg;
enum rfc_6020 keyword;
char *reason = NULL;
int ret;
uint32_t minmax;
switch (ys->ys_keyword){ arg = yang_argument_get(ys);
keyword = yang_keyword_get(ys);
switch (keyword){
case Y_FRACTION_DIGITS: case Y_FRACTION_DIGITS:
if (ys_parse(ys, CGV_UINT8) == NULL) if (ys_parse(ys, CGV_UINT8) == NULL)
goto done; goto done;
@ -3225,7 +3276,7 @@ ys_parse_sub(yang_stmt *ys,
break; break;
case Y_REVISION: case Y_REVISION:
case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */ 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; goto done;
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){ if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
clicon_err(OE_YANG, errno, "cv_new"); clicon_err(OE_YANG, errno, "cv_new");
@ -3234,14 +3285,37 @@ ys_parse_sub(yang_stmt *ys,
cv_uint32_set(ys->ys_cv, date); cv_uint32_set(ys->ys_cv, date);
break; break;
case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */ case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */
if (strcmp(ys->ys_argument, "current") && if (strcmp(arg, "current") &&
strcmp(ys->ys_argument, "deprecated") && strcmp(arg, "deprecated") &&
strcmp(ys->ys_argument, "obsolete")){ strcmp(arg, "obsolete")){
clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", ys->ys_argument); clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", arg);
goto done; goto done;
} }
break; 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 case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
but it may not be */ but it may not be */
if (extra == NULL) if (extra == NULL)
@ -3273,8 +3347,7 @@ int
yang_mandatory(yang_stmt *ys) yang_mandatory(yang_stmt *ys)
{ {
yang_stmt *ym; yang_stmt *ym;
char *reason = NULL; cg_var *cv;
uint8_t min_elements; /* XXX change to 32 (need new cligen version) */
/* 1) A leaf, choice, anydata, or anyxml node with a "mandatory" /* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true". */ * statement with the value "true". */
@ -3289,12 +3362,8 @@ yang_mandatory(yang_stmt *ys)
* value greater than zero. */ * value greater than zero. */
else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){ else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){
if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){ if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
/* XXX change to 32 (need new cligen version) */ cv = yang_cv_get(ym);
if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){ return cv_uint32_get(cv) > 0;
clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8");
return 0; /* XXX ignore error */
}
return min_elements > 0;
} }
} }
/* 3) A container node without a "presence" statement and that has at /* 3) A container node without a "presence" statement and that has at

View file

@ -255,8 +255,8 @@ yms_build(clicon_handle h,
* @param[in] brief Just name, revision and uri (no cache) * @param[in] brief Just name, revision and uri (no cache)
* @param[in,out] xret Existing XML tree, merge x into this * @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal) * @retval -1 Error (fatal)
* @retval 0 OK * @retval 0 Statedata callback failed
* @retval 1 Statedata callback failed * @retval 1 OK
* @notes NYI: schema, deviation * @notes NYI: schema, deviation
x +--ro modules-state x +--ro modules-state
x +--ro module-set-id string x +--ro module-set-id string
@ -288,6 +288,7 @@ yang_modules_state_get(clicon_handle h,
char *msid; /* modules-set-id */ char *msid; /* modules-set-id */
cxobj *x1; cxobj *x1;
cbuf *cb = NULL; cbuf *cb = NULL;
int ret;
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID"); msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if ((x = clicon_modst_cache_get(h, brief)) != NULL){ 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 (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done; goto done;
retval = 1; goto fail;
goto done;
} }
if (xml_rootchild(x, 0, &x) < 0) if (xml_rootchild(x, 0, &x) < 0)
goto done; 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 */ /* Wrap x (again) with new top-level node "top" which merge wants */
if ((x = xml_wrap(x, "top")) < 0) if ((x = xml_wrap(x, "top")) < 0)
goto done; goto done;
if (netconf_trymerge(x, yspec, xret) < 0) if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
} }
retval = 0; retval = 1;
done: done:
clicon_debug(1, "%s %d", __FUNCTION__, retval); clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (cb) if (cb)
@ -337,6 +339,9 @@ yang_modules_state_get(clicon_handle h,
if (x) if (x)
xml_free(x); xml_free(x);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! For single module state with namespace, get revisions and send upgrade callbacks /*! For single module state with namespace, get revisions and send upgrade callbacks

View file

@ -72,8 +72,8 @@ testname=
# eg logging to a file: RCLOG="-l f/www-data/restconf.log" # eg logging to a file: RCLOG="-l f/www-data/restconf.log"
: ${RCLOG:=} : ${RCLOG:=}
# Wait after daemons (backend/restconf) start. Set to 10 if valgrind # Wait after daemons (backend/restconf) start. See mem.sh for valgrind
: ${RCWAIT:=1} : ${RCWAIT:=2}
# Parse yangmodels from https://github.com/YangModels/yang # Parse yangmodels from https://github.com/YangModels/yang
# Recommended: checkout yangmodels elsewhere in the tree and set the env # Recommended: checkout yangmodels elsewhere in the tree and set the env
@ -168,6 +168,21 @@ stop_backend(){
fi fi
} }
# Wait for restconf to stop sending 502 Bad Gateway
wait_backend(){
reply=$(echo '<rpc message-id="101" xmlns="http://clicon.org/lib"><ping/></rpc>]]>]]>' | clixon_netconf -qef $cfg 2> /dev/null)
let i=0;
while [[ $reply != "<rpc-reply"* ]]; do
sleep 1
reply=$(echo '<rpc message-id="101" xmlns="http://clicon.org/lib"><ping/></rpc>]]>]]>' | 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_restconf(){
# Start in background # Start in background
sudo su -c "$clixon_restconf $RCLOG -D $DBG $*" -s /bin/sh www-data & sudo su -c "$clixon_restconf $RCLOG -D $DBG $*" -s /bin/sh www-data &
@ -184,6 +199,21 @@ stop_restconf(){
fi 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 # Increment test number and print a nice string
new(){ new(){
if [ $valgrindtest -eq 1 ]; then if [ $valgrindtest -eq 1 ]; then

View file

@ -33,7 +33,7 @@ memonce(){
valgrindtest=3 # This means backend valgrind test valgrindtest=3 # This means backend valgrind test
sudo chmod 660 $valgrindfile sudo chmod 660 $valgrindfile
sudo chown www-data $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" 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"
;; ;;

View file

@ -20,7 +20,7 @@ cat <<EOF > $cfg
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR> <CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR> <CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR> <CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>system</CLICON_YANG_MODULE_MAIN> <CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR> <CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR> <CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE> <CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
@ -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 EOF
new "test params: -f $cfg -y $fyang" new "test params: -f $cfg"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "kill old backend" new "kill old backend"
@ -97,55 +113,55 @@ if [ $BE -ne 0 ]; then
fi fi
sudo pkill clixon_backend # to be sure sudo pkill clixon_backend # to be sure
new "start backend -s init -f $cfg -y $fyang" new "start backend -s init -f $cfg"
start_backend -s init -f $cfg -y $fyang start_backend -s init -f $cfg
fi fi
new "kill old restconf daemon" new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf
new "start restconf daemon" new "start restconf daemon"
start_restconf -f $cfg -y $fyang start_restconf -f $cfg
new "waiting" new "waiting"
sleep $RCWAIT sleep $RCWAIT
# First vanilla (protocol) case # First vanilla (protocol) case
new "netconf validate empty" new "netconf validate empty"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set protocol both udp and tcp" new "netconf set protocol both udp and tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/><udp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/><udp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate both udp and tcp fail" new "netconf validate both udp and tcp fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>udp</bad-element></error-info><error-severity>error</error-severity><error-message>Element in choice statement already exists</error-message></rpc-error></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>udp</bad-element></error-info><error-severity>error</error-severity><error-message>Element in choice statement already exists</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes" new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set empty protocol" new "netconf set empty protocol"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate protocol" new "netconf validate protocol"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set protocol tcp" new "netconf set protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get protocol tcp" new "netconf get protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></data></rpc-reply>]]>]]>$'
new "netconf commit protocol tcp" new "netconf commit protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)" new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><udp nc:operation="create"/></protocol></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><udp nc:operation="create"/></protocol></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get protocol udp" new "netconf get protocol udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><udp/></protocol></system></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><udp/></protocol></system></data></rpc-reply>]]>]]>$'
new "netconf commit protocol udp" new "netconf commit protocol udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
#-- restconf #-- 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"}}} ' 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" 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" 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" 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" 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" 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" 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) # Second shorthand (no case)
new "netconf set protocol both udp and tcp" new "netconf set protocol both udp and tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/><udp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/><udp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate both udp and tcp fail" new "netconf validate both udp and tcp fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>udp</bad-element></error-info><error-severity>error</error-severity><error-message>Element in choice statement already exists</error-message></rpc-error></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>udp</bad-element></error-info><error-severity>error</error-severity><error-message>Element in choice statement already exists</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes" new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set shorthand tcp" new "netconf set shorthand tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><tcp/></shorthand></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><tcp/></shorthand></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)" new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get shorthand udp" new "netconf get shorthand udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></data></rpc-reply>]]>]]>$'
new "netconf validate shorthand" new "netconf validate shorthand"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes" new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Third check mandatory # Third check mandatory
new "netconf set empty mandatory" new "netconf set empty mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get mandatory empty" new "netconf get mandatory empty"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory/></system></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory/></system></data></rpc-reply>]]>]]>$'
new "netconf validate mandatory" new "netconf validate mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>data-missing</error-tag><error-app-tag>missing-choice</error-app-tag><error-info><missing-choice>name</missing-choice></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>data-missing</error-tag><error-app-tag>missing-choice</error-app-tag><error-info><missing-choice>name</missing-choice></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$'
new "netconf set mandatory udp" new "netconf set mandatory udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get mandatory udp" new "netconf get mandatory udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></data></rpc-reply>]]>]]>$'
new "netconf validate mandatory" new "netconf validate mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Choice with multiple items
new "netconf choice multiple items first"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config" op="replace">
<ma>0</ma>
<ma>1</ma>
<mb>2</mb>
</system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate multiple ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf choice multiple items second"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config" op="replace">
<mb>0</mb>
<mb>1</mb>
</system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate multiple ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf choice multiple items mix"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config" op="replace">
<ma>0</ma>
<mb>2</mb>
<mc>2</mc>
</system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate multiple error"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>mc</bad-element></error-info><error-severity>error</error-severity><error-message>Element in choice statement already exists</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF
cat <<EOF > $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 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1><b1>0</b1></c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: minimal validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: maximal"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a0><k>0</k></a0><a0><k>1</k></a0><a0><k>unbounded</k></a0><a1><k>0</k></a1><a1><k>1</k></a1><b0>0</b0><b0>1</b0><b0>unbounded</b0><b1>0</b1><b0>1</b0></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"/></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
# NYI
if false; then # nyi
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
new "minmax: no list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><b1>0</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
new "minmax: no leaf-list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
new "minmax: Too large list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1><a1><k>1</k></a1><a1><k>2</k></a1><b1>0</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
new "minmax: Too large leaf-list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1><b1>0</b1><b1>1</b1><b1>2</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
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

241
test/test_minmax.sh Executable file
View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF
cat <<EOF > $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 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon">
<a1><k>0</k></a1>
<b1>0</b1>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: minimal validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: maximal"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a0><k>0</k></a0><a0><k>1</k></a0><a0><k>unbounded</k></a0><a1><k>0</k></a1><a1><k>1</k></a1><b0>0</b0><b0>1</b0><b0>unbounded</b0><b1>0</b1><b0>1</b0></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax: empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"/></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
#XXX echo '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' |$clixon_netconf -qf $cfg
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>c</error-path></rpc-error></rpc-reply>]]>]]>$'
new "minmax: no list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><b1>0</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>c</error-path></rpc-error></rpc-reply>]]>]]>$'
new "minmax: no leaf-list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-few-elements</error-app-tag><error-severity>error</error-severity><error-path>c</error-path></rpc-error></rpc-reply>]]>]]>$'
new "minmax: Too large list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1><a1><k>1</k></a1><a1><k>2</k></a1><b1>0</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>a1</error-path></rpc-error></rpc-reply>]]>]]>$'
new "minmax: Too large leaf-list"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><a1><k>0</k></a1><b1>0</b1><b1>1</b1><b1>2</b1></c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>b1</error-path></rpc-error></rpc-reply>]]>]]>$'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# With choice c1
new "minmax choice: minimal"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c2 xmlns="urn:example:clixon">
<a1><k>0</k></a1>
<b1>0</b1>
</c2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax choice: minimal validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax choice: many a2"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c2 xmlns="urn:example:clixon">
<a2><k>0</k></a2>
<a2><k>1</k></a2>
<a2><k>2</k></a2>
<b1>0</b1>
</c2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax choice: many a2 validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax choice: too many a1"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c2 xmlns="urn:example:clixon">
<a1><k>0</k></a1>
<a1><k>1</k></a1>
<a1><k>2</k></a1>
<b1>0</b1>
</c2></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax choice: too many validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>a1</error-path></rpc-error></rpc-reply>]]>]]>$'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# top config level
new "minmax top level"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config>
<b3 xmlns="urn:example:clixon">0</b3>
<b3 xmlns="urn:example:clixon">1</b3>
</config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax top level ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax top level too many"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config>
<b3 xmlns="urn:example:clixon">0</b3>
<b3 xmlns="urn:example:clixon">1</b3>
<b3 xmlns="urn:example:clixon">2</b3>
</config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "minmax top level too many should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>too-many-elements</error-app-tag><error-severity>error</error-severity><error-path>b3</error-path></rpc-error></rpc-reply>]]>]]>'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
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

View file

@ -106,7 +106,9 @@ EOF
start_restconf -f $cfg -- -a start_restconf -f $cfg -- -a
new "waiting" new "waiting"
sleep $RCWAIT wait_backend
wait_restconf
#----------- First get #----------- First get
case "$ret1" in case "$ret1" in
@ -119,6 +121,7 @@ EOF
' '
;; ;;
esac esac
new "get startup 42" new "get startup 42"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 "$ret" 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)" new "nacm enabled, exec default permit, write permit (expect fail)"
testrun true deny permit permit 2 0 2 testrun true deny permit permit 2 0 2
rm -rf $dir rm -rf $dir

View file

@ -221,6 +221,24 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><can
new "verify list user order (as entered)" new "verify list user order (as entered)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/y2"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>c</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>fie</a></y2></data></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/y2"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>c</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>fie</a></y2></data></rpc-reply>]]>]]>$'
new "Overwrite existing ordered-by user y2->c"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order">
<k>c</k><a>newc</a>
</y2></config></edit-config></rpc>]]>]]>'
new "Overwrite existing ordered-by user y2->b"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order">
<k>b</k><a>newb</a>
</y2></config></edit-config></rpc>]]>]]>'
new "Overwrite existing ordered-by user y2->a"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y2 xmlns="urn:example:order">
<k>a</k><a>newa</a>
</y2></config></edit-config></rpc>]]>]]>'
new "Tests for no duplicates."
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/y2"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>c</k><a>newc</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>newb</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>newa</a></y2></data></rpc-reply>]]>]]>$'
#-- order by type rather than strings. #-- order by type rather than strings.
# there are three leaf-lists:strings, ints, and decimal64, and two lists: # there are three leaf-lists:strings, ints, and decimal64, and two lists:
# listints and listdecs # listints and listdecs

View file

@ -53,7 +53,8 @@ new "start restconf daemon"
start_restconf -f $cfg start_restconf -f $cfg
new "waiting" new "waiting"
sleep $RCWAIT wait_backend
wait_restconf
new "restconf tests" new "restconf tests"
@ -73,12 +74,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r
# Should be alphabetically ordered # Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)" 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)" new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"/></operations>' expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"/></operations>'
match=`echo $ret | grep -EZo "$expect"` match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"

View file

@ -85,7 +85,8 @@ new "start restconf daemon"
start_restconf -f $cfg -y $fyang start_restconf -f $cfg -y $fyang
new "waiting" new "waiting"
sleep $RCWAIT wait_backend
wait_restconf
new "restconf tests" new "restconf tests"

View file

@ -25,7 +25,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example APPNAME=example
: ${clixon_util_stream:=clixon_util_stream} : ${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") DATE=$(date +"%Y-%m-%d")
@ -120,7 +120,8 @@ new "start restconf daemon"
start_restconf -f $cfg -y $fyang start_restconf -f $cfg -y $fyang
new "waiting" new "waiting"
sleep $RCWAIT wait_backend
wait_restconf
# #
# 1. Netconf RFC5277 stream testing # 1. Netconf RFC5277 stream testing

226
test/test_unique.sh Executable file
View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF
# Example (the list server part) from RFC7950 Sec 7.8.3.1 w changed types
cat <<EOF > $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 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><server>
<name>smtp</name>
<ip>192.0.2.1</ip>
<port>25</port>
</server>
<server>
<name>http</name>
<ip>192.0.2.1</ip>
<port>25</port>
</server>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique><ip>192.0.2.1</ip></non-unique><non-unique><port>25</port></non-unique></error-info></rpc-error></rpc-reply>]]>]]>$'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Add valid example"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><server>
<name>smtp</name>
<ip>192.0.2.1</ip>
<port>25</port>
</server>
<server>
<name>http</name>
<ip>192.0.2.1</ip>
</server>
<server>
<name>ftp</name>
<ip>192.0.2.1</ip>
</server>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "make it invalid by adding port to ftp entry"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c xmlns="urn:example:clixon"><server><name>ftp</name><port operation="create">25</port></server>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique><ip>192.0.2.1</ip></non-unique><non-unique><port>25</port></non-unique></error-info></rpc-error></rpc-reply>]]>]]>$'
new "make it valid by deleting port from smtp entry"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c xmlns="urn:example:clixon"><server><name>smtp</name><port operation="delete">25</port></server>
</c></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Then test single-field case
new "Add not valid example"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"><single>
<name>smtp</name>
<ip>192.0.2.1</ip>
</single>
<single>
<name>http</name>
<ip>192.0.2.1</ip>
</single>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique><ip>192.0.2.1</ip></non-unique></error-info></rpc-error></rpc-reply>]]>]]>$'
new "make valid by replacing IP of http entry"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>none</default-operation><config><c xmlns="urn:example:clixon"><single><name>http</name><ip>178.23.34.1</ip></single>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate ok"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Then test composite case (detect duplicates among other elements)
# and also unordered
new "Add not valid example"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon">
<b>other</b>
<single>
<name>smtp</name>
<ip>192.0.2.1</ip>
</single>
<a>other</a>
<server>
<name>smtp</name>
<ip>192.0.2.1</ip>
<port>25</port>
</server>
<single>
<name>http</name>
<ip>192.0.2.1</ip>
</single>
<other><a>xx</a></other>
<server>
<name>http</name>
<ip>192.0.2.1</ip>
<port>25</port>
</server>
</c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique><ip>192.0.2.1</ip></non-unique><non-unique><port>25</port></non-unique></error-info></rpc-error></rpc-reply>]]>]]>$'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
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

View file

@ -52,4 +52,7 @@ module clixon-lib {
} }
} }
} }
rpc ping {
description "Check aliveness of backend daemon.";
}
} }