Non-key list now not accepted in edit-config (before only on validation)
This commit is contained in:
parent
4e23864acd
commit
06e6ef80d1
25 changed files with 620 additions and 71 deletions
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
### 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)
|
||||
* Restconf with startup feature will now copy all edit changes to startup db (as it should according to RFC 8040)
|
||||
* Netconf Startup feature is no longer hardcoded, you need to explicitly enable it (See RFC 6241, Section 8.7)
|
||||
* Enable in config file with: `<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>`, or use `*:*`
|
||||
|
|
|
|||
|
|
@ -433,6 +433,7 @@ from_client_edit_config(clicon_handle h,
|
|||
*/
|
||||
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
|
||||
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
|
||||
goto done;
|
||||
if (non_config){
|
||||
|
|
@ -440,6 +441,11 @@ from_client_edit_config(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* xmldb_put (difflist handling) requires list keys */
|
||||
if ((ret = xml_yang_validate_list_key_only(xc, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
/* Cant do this earlier since we dont have a yang spec to
|
||||
* the upper part of the tree, until we get the "config" tree.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ generic_validate(yang_stmt *yspec,
|
|||
* 4. Validate startup db. (valid)
|
||||
* 5. If valid fails, call startup-cb(Invalid, msdiff), keep startup in candidate and commit failsafe db. Done.
|
||||
* 6. Call startup-cb(OK, msdiff) and commit.
|
||||
* @see from_validate_common for incoming validate/commit
|
||||
*/
|
||||
static int
|
||||
startup_common(clicon_handle h,
|
||||
|
|
@ -181,10 +182,14 @@ startup_common(clicon_handle h,
|
|||
if ((msd = modstate_diff_new()) == NULL)
|
||||
goto done;
|
||||
clicon_debug(1, "Reading startup config from %s", db);
|
||||
if (xmldb_get(h, db, "/", &xt, msd) < 0)
|
||||
if (xmldb_get1(h, db, "/", &xt, msd) < 0)
|
||||
goto done;
|
||||
/* Clear flags xpath for get */
|
||||
xml_apply0(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||
(void*)(XML_FLAG_MARK|XML_FLAG_CHANGE));
|
||||
if (xml_child_nr(xt) == 0){ /* If empty skip */
|
||||
td->td_target = xt;
|
||||
xt = NULL;
|
||||
goto ok;
|
||||
}
|
||||
if (msd){
|
||||
|
|
@ -204,6 +209,7 @@ startup_common(clicon_handle h,
|
|||
goto done;
|
||||
/* Handcraft transition with with only add tree */
|
||||
td->td_target = xt;
|
||||
xt = NULL;
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(td->td_target, x, CX_ELMNT)) != NULL){
|
||||
if (cxvec_append(x, &td->td_avec, &td->td_alen) < 0)
|
||||
|
|
@ -232,6 +238,8 @@ startup_common(clicon_handle h,
|
|||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (msd)
|
||||
modstate_diff_free(msd);
|
||||
return retval;
|
||||
|
|
@ -267,14 +275,23 @@ startup_validate(clicon_handle h,
|
|||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Clear cached trees from default values and marking */
|
||||
if (xmldb_get1_clear(h, db) < 0)
|
||||
goto done;
|
||||
if (xtr){
|
||||
*xtr = td->td_target;
|
||||
td->td_target = NULL;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
if (td)
|
||||
if (td){
|
||||
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){
|
||||
/* xmldb_get1 requires free only if not cache */
|
||||
td->td_target = NULL;
|
||||
td->td_src = NULL;
|
||||
}
|
||||
transaction_free(td);
|
||||
}
|
||||
return retval;
|
||||
fail: /* cbret should be set */
|
||||
retval = 0;
|
||||
|
|
@ -314,6 +331,10 @@ startup_commit(clicon_handle h,
|
|||
/* 8. Call plugin transaction commit callbacks */
|
||||
if (plugin_transaction_commit(h, td) < 0)
|
||||
goto done;
|
||||
/* Clear cached trees from default values and marking */
|
||||
if (xmldb_get1_clear(h, db) < 0)
|
||||
goto done;
|
||||
|
||||
/* [Delete and] create running db */
|
||||
if (xmldb_exists(h, "running") == 1){
|
||||
if (xmldb_delete(h, "running") != 0 && errno != ENOENT)
|
||||
|
|
@ -334,8 +355,14 @@ startup_commit(clicon_handle h,
|
|||
|
||||
retval = 1;
|
||||
done:
|
||||
if (td)
|
||||
if (td){
|
||||
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){
|
||||
/* xmldb_get1 requires free only if not cache */
|
||||
td->td_target = NULL;
|
||||
td->td_src = NULL;
|
||||
}
|
||||
transaction_free(td);
|
||||
}
|
||||
return retval;
|
||||
fail: /* cbret should be set */
|
||||
retval = 0;
|
||||
|
|
@ -352,6 +379,7 @@ startup_commit(clicon_handle h,
|
|||
* @retval 1 Validation OK
|
||||
* @note Need to differentiate between error and validation fail
|
||||
* (only done for generic_validate)
|
||||
* @see startup_common for startup scenario
|
||||
*/
|
||||
static int
|
||||
from_validate_common(clicon_handle h,
|
||||
|
|
@ -511,6 +539,16 @@ candidate_commit(clicon_handle h,
|
|||
*/
|
||||
if (xmldb_copy(h, candidate, "running") < 0)
|
||||
goto done;
|
||||
/* Here pointers to old (source) tree are obsolete */
|
||||
if (td->td_dvec){
|
||||
td->td_dlen = 0;
|
||||
free(td->td_dvec);
|
||||
td->td_dvec = NULL;
|
||||
}
|
||||
if (td->td_scvec){
|
||||
free(td->td_scvec);
|
||||
td->td_scvec = NULL;
|
||||
}
|
||||
|
||||
/* 9. Call plugin transaction end callbacks */
|
||||
plugin_transaction_end(h, td);
|
||||
|
|
|
|||
|
|
@ -709,7 +709,8 @@ main(int argc,
|
|||
demonized errors OK. Before this stage, errors are logged on stderr
|
||||
also */
|
||||
if (foreground==0){
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
|
||||
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
|
||||
logdst==CLICON_LOG_FILE?CLICON_LOG_FILE:CLICON_LOG_SYSLOG);
|
||||
if (daemon(0, 0) < 0){
|
||||
fprintf(stderr, "config: daemon");
|
||||
exit(-1);
|
||||
|
|
|
|||
|
|
@ -262,11 +262,6 @@ startup_extraxml(clicon_handle h,
|
|||
goto fail;
|
||||
if (xt==NULL || xml_child_nr(xt)==0)
|
||||
goto ok;
|
||||
/* Write (potentially modified) xml tree xt back to tmp
|
||||
*/
|
||||
if ((ret = xmldb_put(h, "tmp", OP_REPLACE, xt,
|
||||
clicon_username_get(h), cbret)) < 0)
|
||||
goto done;
|
||||
/* Merge tmp into running (no commit) */
|
||||
if ((ret = db_merge(h, db, "running", cbret)) < 0)
|
||||
goto fail;
|
||||
|
|
@ -275,9 +270,9 @@ startup_extraxml(clicon_handle h,
|
|||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
if (xt)
|
||||
if (xt && !clicon_option_bool(h, "CLICON_XMLDB_CACHE"))
|
||||
xml_free(xt);
|
||||
if (xmldb_delete(h, "tmp") != 0 && errno != ENOENT)
|
||||
if (xmldb_delete(h, db) != 0 && errno != ENOENT)
|
||||
return -1;
|
||||
return retval;
|
||||
fail:
|
||||
|
|
|
|||
|
|
@ -188,6 +188,9 @@ transaction_clen(transaction_data td)
|
|||
return ((transaction_data_t *)td)->td_clen;
|
||||
}
|
||||
|
||||
/*! Print transaction on FILE for debug
|
||||
* @see transaction_log
|
||||
*/
|
||||
int
|
||||
transaction_print(FILE *f,
|
||||
transaction_data th)
|
||||
|
|
@ -218,3 +221,50 @@ transaction_print(FILE *f,
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
transaction_log(clicon_handle h,
|
||||
transaction_data th,
|
||||
int level,
|
||||
const char *op)
|
||||
{
|
||||
cxobj *xn;
|
||||
int i;
|
||||
transaction_data_t *td;
|
||||
cbuf *cb = NULL;
|
||||
|
||||
td = (transaction_data_t *)th;
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_CFG, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
for (i=0; i<td->td_dlen; i++){
|
||||
xn = td->td_dvec[i];
|
||||
clicon_xml2cbuf(cb, xn, 0, 0);
|
||||
}
|
||||
if (i)
|
||||
clicon_log(level, "%s %" PRIu64 " %s del: %s",
|
||||
__FUNCTION__, td->td_id, op, cbuf_get(cb));
|
||||
cbuf_reset(cb);
|
||||
for (i=0; i<td->td_alen; i++){
|
||||
xn = td->td_avec[i];
|
||||
clicon_xml2cbuf(cb, xn, 0, 0);
|
||||
}
|
||||
if (i)
|
||||
clicon_log(level, "%s %" PRIu64 " %s add: %s", __FUNCTION__, td->td_id, op, cbuf_get(cb));
|
||||
cbuf_reset(cb);
|
||||
for (i=0; i<td->td_clen; i++){
|
||||
if (td->td_scvec){
|
||||
xn = td->td_scvec[i];
|
||||
clicon_xml2cbuf(cb, xn, 0, 0);
|
||||
}
|
||||
xn = td->td_tcvec[i];
|
||||
clicon_xml2cbuf(cb, xn, 0, 0);
|
||||
}
|
||||
if (i)
|
||||
clicon_log(level, "%s %" PRIu64 " %s change: %s", __FUNCTION__, td->td_id, op, cbuf_get(cb));
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,6 @@ cxobj **transaction_tcvec(transaction_data td);
|
|||
size_t transaction_clen(transaction_data td);
|
||||
|
||||
int transaction_print(FILE *f, transaction_data th);
|
||||
int transaction_log(clicon_handle h, transaction_data th, int level, const char *id);
|
||||
|
||||
#endif /* _CLIXON_BACKEND_TRANSACTION_H_ */
|
||||
|
|
|
|||
|
|
@ -656,6 +656,12 @@ cli_show_auto(clicon_handle h,
|
|||
// goto done;
|
||||
if (api_path_fmt2xpath(api_path_fmt, cvv, &xpath) < 0)
|
||||
goto done;
|
||||
/* XXX Kludge to overcome a trailing / in show, that I cannot add to
|
||||
* yang2api_path_fmt_1 where it should belong.
|
||||
*/
|
||||
if (xpath[strlen(xpath)-1] == '/')
|
||||
xpath[strlen(xpath)-1] = '\0';
|
||||
|
||||
/* Get configuration from database */
|
||||
if (clicon_rpc_get_config(h, db, xpath, &xt) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -72,23 +72,47 @@ static int _state = 0;
|
|||
*/
|
||||
static int _upgrade = 0;
|
||||
|
||||
/*! Variable to control transaction logging (for debug)
|
||||
* If set, call syslog for every transaction callback
|
||||
*/
|
||||
static int _transaction_log = 0;
|
||||
|
||||
/* forward */
|
||||
static int example_stream_timer_setup(clicon_handle h);
|
||||
|
||||
int
|
||||
main_begin(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "begin");
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*! This is called on validate (and commit). Check validity of candidate
|
||||
*/
|
||||
int
|
||||
transaction_validate(clicon_handle h,
|
||||
main_validate(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
// transaction_print(stderr, td);
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "validate");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main_complete(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! This is called on commit. Identify modifications and adjust machine state
|
||||
*/
|
||||
int
|
||||
transaction_commit(clicon_handle h,
|
||||
main_commit(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
cxobj *target = transaction_target(td); /* wanted XML tree */
|
||||
|
|
@ -96,7 +120,8 @@ transaction_commit(clicon_handle h,
|
|||
int i;
|
||||
size_t len;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "commit");
|
||||
/* Get all added i/fs */
|
||||
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
|
||||
return -1;
|
||||
|
|
@ -109,6 +134,24 @@ transaction_commit(clicon_handle h,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main_end(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "end");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main_abort(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
if (_transaction_log)
|
||||
transaction_log(h, td, LOG_NOTICE, "abort");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Routing example notification timer handler. Here is where the periodic action is
|
||||
*/
|
||||
static int
|
||||
|
|
@ -521,12 +564,12 @@ static clixon_plugin_api api = {
|
|||
example_exit, /* exit */
|
||||
.ca_reset=example_reset, /* reset */
|
||||
.ca_statedata=example_statedata, /* statedata */
|
||||
.ca_trans_begin=NULL, /* trans begin */
|
||||
.ca_trans_validate=transaction_validate,/* trans validate */
|
||||
.ca_trans_complete=NULL, /* trans complete */
|
||||
.ca_trans_commit=transaction_commit, /* trans commit */
|
||||
.ca_trans_end=NULL, /* trans end */
|
||||
.ca_trans_abort=NULL /* trans abort */
|
||||
.ca_trans_begin=main_begin, /* trans begin */
|
||||
.ca_trans_validate=main_validate, /* trans validate */
|
||||
.ca_trans_complete=main_complete, /* trans complete */
|
||||
.ca_trans_commit=main_commit, /* trans commit */
|
||||
.ca_trans_end=main_end, /* trans end */
|
||||
.ca_trans_abort=main_abort /* trans abort */
|
||||
};
|
||||
|
||||
/*! Backend plugin initialization
|
||||
|
|
@ -551,7 +594,7 @@ clixon_plugin_init(clicon_handle h)
|
|||
goto done;
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, "rsu")) != -1)
|
||||
while ((c = getopt(argc, argv, "rsut")) != -1)
|
||||
switch (c) {
|
||||
case 'r':
|
||||
_reset = 1;
|
||||
|
|
@ -562,6 +605,9 @@ clixon_plugin_init(clicon_handle h)
|
|||
case 'u':
|
||||
_upgrade = 1;
|
||||
break;
|
||||
case 't': /* transaction log */
|
||||
_transaction_log = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Example stream initialization:
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
|
|||
int xml_yang_root(cxobj *x, cxobj **xr);
|
||||
int xmlns_assign(cxobj *x);
|
||||
int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret);
|
||||
int xml_yang_validate_list_key_only(cxobj *xt, cbuf *cbret);
|
||||
int xml_yang_validate_add(cxobj *xt, cbuf *cbret);
|
||||
int xml_yang_validate_all(cxobj *xt, cbuf *cbret);
|
||||
int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret);
|
||||
|
|
@ -72,6 +73,8 @@ int xml_spec_populate(cxobj *x, void *arg);
|
|||
int api_path2xpath(yang_stmt *yspec, cvec *cvv, int offset, cbuf *xpath);
|
||||
int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
|
||||
yang_class nodeclass, int strict, cxobj **xpathp, yang_stmt **ypathp);
|
||||
|
||||
int xml2xpath(cxobj *x, char **xpath);
|
||||
int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason);
|
||||
int yang_enum_int_value(cxobj *node, int32_t *val);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
* Saves Clixon data as clear-text XML (or JSON)
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,16 @@ struct indexhead{
|
|||
};
|
||||
typedef struct indexhead indexhead;
|
||||
|
||||
static int
|
||||
indexhead_free(indexhead *ih)
|
||||
{
|
||||
indexlist *il;
|
||||
|
||||
while ((il = ih->ih_list) != NULL)
|
||||
DELQ(il, ih->ih_list, indexlist*);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
index_get(indexhead *ih,
|
||||
size_t len, /* in bytes */
|
||||
|
|
@ -190,7 +200,7 @@ buf2xml(FILE *f,
|
|||
cxobj *x = NULL;
|
||||
char *name;
|
||||
char *prefix = NULL;
|
||||
char hdr[8];
|
||||
char hdr[8] = {0, };
|
||||
char *buf = NULL;
|
||||
int vlen;
|
||||
int i;
|
||||
|
|
@ -362,7 +372,8 @@ xml2buf(FILE *f,
|
|||
else{
|
||||
uint32_t index;
|
||||
for (i=0; i< xml_child_nr(x); i++){
|
||||
if (xml2buf(f, xml_child_i(x, i), ih, &index) < 0)
|
||||
if (xml2buf(f, xml_child_i(x, i), ih,
|
||||
&index) < 0)
|
||||
goto done;
|
||||
index = htonl(index);
|
||||
memcpy(&buf[ptr], &index, sizeof(uint32_t));
|
||||
|
|
@ -376,10 +387,6 @@ xml2buf(FILE *f,
|
|||
goto done;
|
||||
}
|
||||
memcpy(buf0, hdr, sizeof(hdr));
|
||||
if (0 && fwrite(hdr, sizeof(char), sizeof(hdr), f) < 0){
|
||||
clicon_err(OE_UNIX, errno, "fwrite");
|
||||
goto done;
|
||||
}
|
||||
if (fwrite(buf0, sizeof(char), len, f) < 0){
|
||||
clicon_err(OE_XML, errno, "fwrite");
|
||||
goto done;
|
||||
|
|
@ -401,7 +408,6 @@ datastore_tree_write(clicon_handle h,
|
|||
int retval = -1;
|
||||
FILE *f = NULL;
|
||||
indexhead ih = {0,};
|
||||
indexlist *il;
|
||||
|
||||
if ((f = fopen(filename, "w+b")) == NULL){
|
||||
clicon_err(OE_XML, errno, "Opening file %s", filename);
|
||||
|
|
@ -413,8 +419,7 @@ datastore_tree_write(clicon_handle h,
|
|||
index_dump(stderr, &ih);
|
||||
retval = 0;
|
||||
done:
|
||||
while ((il = ih.ih_list) != NULL)
|
||||
DELQ(il, ih.ih_list, indexlist*);
|
||||
indexhead_free(&ih);
|
||||
if (f != NULL)
|
||||
fclose(f);
|
||||
return retval;
|
||||
|
|
@ -439,7 +444,8 @@ datastore_tree_read(clicon_handle h,
|
|||
}
|
||||
if ((retval = buf2xml(f, 0, xt)) < 0)
|
||||
goto done;
|
||||
if (retval == 0){ /* fail */
|
||||
if (retval == 0 || /* fail */
|
||||
*xt == NULL){ /* empty */
|
||||
if ((*xt = xml_new("config", NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(*xt, CX_ELMNT);
|
||||
|
|
|
|||
|
|
@ -633,6 +633,44 @@ check_mandatory(cxobj *xt,
|
|||
goto done;
|
||||
}
|
||||
|
||||
static int
|
||||
check_list_key(cxobj *xt,
|
||||
yang_stmt *yt,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
yang_stmt *yc;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
cg_var *cvi;
|
||||
char *keyname;
|
||||
|
||||
for (i=0; i<yt->ys_len; i++){
|
||||
yc = yt->ys_stmt[i];
|
||||
/* Check if a list does not have mandatory key leafs */
|
||||
if (yt->ys_keyword == Y_LIST &&
|
||||
yc->ys_keyword == Y_KEY &&
|
||||
yang_config(yt)){
|
||||
cvk = yt->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
|
||||
cvi = NULL;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
||||
keyname = cv_string_get(cvi);
|
||||
if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){
|
||||
if (netconf_missing_element(cbret, "application", keyname, "Mandatory key") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Validate a single XML node with yang specification for added entry
|
||||
* 1. Check if mandatory leafs present as subs.
|
||||
* 2. Check leaf values, eg int ranges and string regexps.
|
||||
|
|
@ -737,6 +775,41 @@ xml_yang_validate_add(cxobj *xt,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*! Some checks done only at edit_config, eg keys in lists
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_list_key_only(cxobj *xt,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yt; /* yang spec of xt going in */
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
/* if not given by argument (overide) use default link
|
||||
and !Node has a config sub-statement and it is false */
|
||||
if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
|
||||
if ((ret = check_list_key(xt, yt, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_yang_validate_list_key_only(x, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
||||
/*! Validate a single XML node with yang specification for all (not only added) entries
|
||||
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
|
||||
* @param[in] xt XML node to be validated
|
||||
|
|
@ -1126,11 +1199,13 @@ xml_diff1(yang_stmt *ys,
|
|||
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
|
||||
goto done;
|
||||
x0c = xml_child_each(x0, x0c, CX_ELMNT);
|
||||
continue;
|
||||
}
|
||||
else if (eq > 0){
|
||||
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
|
||||
goto done;
|
||||
x1c = xml_child_each(x1, x1c, CX_ELMNT);
|
||||
continue;
|
||||
}
|
||||
else{ /* equal */
|
||||
if ((yc = xml_spec(x0c)) == NULL){
|
||||
|
|
@ -1257,9 +1332,22 @@ yang2api_path_fmt_1(yang_stmt *ys,
|
|||
yp->ys_keyword != Y_SUBMODULE){
|
||||
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
|
||||
goto done;
|
||||
if (yp->ys_keyword != Y_CHOICE && yp->ys_keyword != Y_CASE)
|
||||
if (yp->ys_keyword != Y_CHOICE && yp->ys_keyword != Y_CASE){
|
||||
#if 0
|
||||
/* In some cases, such as cli_show_auto, a trailing '/' should
|
||||
* NOT be present if ys is a key in a list.
|
||||
* But in other cases (I think most), the / should be there,
|
||||
* so a patch is added in cli_show_auto instead.
|
||||
*/
|
||||
if (ys->ys_keyword == Y_LEAF && yp &&
|
||||
yp->ys_keyword == Y_LIST &&
|
||||
yang_key_match(yp, ys->ys_argument) == 1)
|
||||
;
|
||||
else
|
||||
#endif
|
||||
cprintf(cb, "/");
|
||||
}
|
||||
}
|
||||
else /* top symbol - mark with name prefix */
|
||||
cprintf(cb, "/%s:", yp->ys_argument);
|
||||
|
||||
|
|
@ -2226,6 +2314,112 @@ api_path2xml(char *api_path,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*! Given an XML node, build an xpath to root, internal function
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error. eg XML malformed
|
||||
*/
|
||||
static int
|
||||
xml2xpath1(cxobj *x,
|
||||
cbuf *cb)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xp;
|
||||
yang_stmt *y = NULL;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
cg_var *cvi;
|
||||
char *keyname;
|
||||
cxobj *xkey;
|
||||
cxobj *xb;
|
||||
char *b;
|
||||
enum rfc_6020 keyword;
|
||||
|
||||
if ((xp = xml_parent(x)) != NULL &&
|
||||
xml_spec(xp) != NULL)
|
||||
xml2xpath1(xp, cb);
|
||||
/* XXX: sometimes there should be a /, sometimes not */
|
||||
cprintf(cb, "/%s", xml_name(x));
|
||||
if ((y = xml_spec(x)) != NULL){
|
||||
keyword = yang_keyword_get(y);
|
||||
if (keyword == Y_LEAF_LIST){
|
||||
if ((b = xml_body(x)) != NULL)
|
||||
cprintf(cb, "[.=\"%s\"]", b);
|
||||
else
|
||||
cprintf(cb, "[.=\"\"]");
|
||||
} else if (keyword == Y_LIST){
|
||||
cvk = yang_cvec_get(y);
|
||||
cvi = NULL;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
||||
keyname = cv_string_get(cvi);
|
||||
if ((xkey = xml_find(x, keyname)) == NULL)
|
||||
goto done; /* No key in xml */
|
||||
if ((xb = xml_find(x, keyname)) == NULL)
|
||||
goto done;
|
||||
b = xml_body(xb);
|
||||
cprintf(cb, "[%s=\"%s\"]", keyname, b?b:"");
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Given an XML node, build an xpath to root
|
||||
* Builds only unqualified xpaths, ie no predicates []
|
||||
* @param[in] x XML object
|
||||
* @param[out] xpath Malloced xpath string. Need to free() after use
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error. (eg XML malformed)
|
||||
*/
|
||||
int
|
||||
xml2xpath(cxobj *x,
|
||||
char **xpathp)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb;
|
||||
char *xpath = NULL;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (xml2xpath1(x, cb) < 0)
|
||||
goto done;
|
||||
/* XXX: see xpath in test statement,.. */
|
||||
xpath = cbuf_get(cb);
|
||||
#if 0 /* debug test */
|
||||
{
|
||||
cxobj *xt = x;
|
||||
cxobj *xcp;
|
||||
cxobj *x2;
|
||||
while (xml_parent(xt) != NULL &&
|
||||
xml_spec(xt) != NULL)
|
||||
xt = xml_parent(xt);
|
||||
xcp = xml_parent(xt);
|
||||
xml_parent_set(xt, NULL);
|
||||
x2 = xpath_first(xt, "%s", xpath); /* +1: skip first / */
|
||||
xml_parent_set(xt, xcp);
|
||||
assert(x2 && x==x2);
|
||||
if (x==x2)
|
||||
clicon_debug(1, "%s %s match", __FUNCTION__, xpath);
|
||||
else
|
||||
clicon_debug(1, "%s %s no match", __FUNCTION__, xpath);
|
||||
}
|
||||
#endif
|
||||
if (xpathp){
|
||||
if ((*xpathp = strdup(xpath)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
xpath = NULL;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Check if the module tree x is in is assigned right XML namespace, assign if not
|
||||
* @param[in] x XML node
|
||||
*(0. You should probably find the XML root and apply this function to that.)
|
||||
|
|
|
|||
|
|
@ -715,17 +715,15 @@ xp_relop(xp_ctx *xc1,
|
|||
s1 = xml_body(x);
|
||||
switch(op){
|
||||
case XO_EQ:
|
||||
if (s1==NULL || s2==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "Malformed xpath: empty string");
|
||||
goto done;
|
||||
}
|
||||
if (s1 == NULL || s2 == NULL)
|
||||
xr->xc_bool = (s1==NULL && s2 == NULL);
|
||||
else
|
||||
xr->xc_bool = (strcmp(s1, s2)==0);
|
||||
break;
|
||||
case XO_NE:
|
||||
if (s1==NULL || s2==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "Malformed xpath: empty string");
|
||||
goto done;
|
||||
}
|
||||
if (s1 == NULL || s2 == NULL)
|
||||
xr->xc_bool = !(s1==NULL && s2 == NULL);
|
||||
else
|
||||
xr->xc_bool = (strcmp(s1, s2));
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
34
test/long.sh
34
test/long.sh
|
|
@ -7,11 +7,14 @@
|
|||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Number of list/leaf-list entries in file
|
||||
: ${perfnr:=1000}
|
||||
: ${perfnr:=5000}
|
||||
|
||||
# Number of requests made get/put
|
||||
: ${perfreq:=100}
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/scaling-conf.xml
|
||||
|
|
@ -50,11 +53,14 @@ cat <<EOF > $cfg
|
|||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
sudo callgrind_control -i off
|
||||
|
||||
new "test params: -f $cfg -y $fyang"
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
|
|
@ -76,17 +82,39 @@ start_restconf -f $cfg -y $fyang
|
|||
new "waiting"
|
||||
sleep $RCWAIT
|
||||
|
||||
new "generate 'large' config with $perfnr list entries"
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\">" > $fconfig
|
||||
for (( i=0; i<$perfnr; i++ )); do
|
||||
echo -n "<y><a>$i</a><b>$i</b></y>" >> $fconfig
|
||||
done
|
||||
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
|
||||
|
||||
# Now take large config file and write it via netconf to candidate
|
||||
new "netconf write large config"
|
||||
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# Now commit it from candidate to running
|
||||
new "netconf commit large config"
|
||||
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# Zero all event counters
|
||||
sudo callgrind_control -i on
|
||||
sudo callgrind_control -z
|
||||
|
||||
while [ 1 ] ; do
|
||||
new "restconf add $perfreq small config"
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
#echo "i $i"
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
curl -s -X PUT http://localhost/restconf/data/scaling:x/y=$rnd -d '{"scaling:y":{"a":"'$rnd'","b":"'$rnd'"}}'
|
||||
done
|
||||
|
||||
done
|
||||
new "restconf get $perfreq small config"
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd,$rnd > /dev/null
|
||||
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd,42 > /dev/null
|
||||
done
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# Stop on first error
|
||||
|
||||
# Run valgrindtest once, args:
|
||||
# what: cli|netconf|restconf|backend
|
||||
# what: (cli|netconf|restconf|backend)* # no args means all
|
||||
memonce(){
|
||||
what=$1
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ APPNAME=example
|
|||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/nacm-example.yang
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
|
|
@ -29,6 +32,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ cat <<EOF > $cfg
|
|||
</clixon-config>
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg"
|
||||
new "test params: -f $cfg -- -s"
|
||||
# Bring your own backend
|
||||
if [ $BE -ne 0 ]; then
|
||||
# kill old backend (if any)
|
||||
|
|
@ -88,10 +88,7 @@ new "Re-Delete eth/0/0 using none should generate error"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="delete"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error>'
|
||||
|
||||
new "Add interface without key"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="create"><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf validate missing key"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>name</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory key</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="create"><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>name</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory key</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf discard-changes"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
|
|
@ -37,6 +40,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dbdir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
# Number of list/leaf-list entries in file
|
||||
: ${perfnr:=10000}
|
||||
|
||||
|
|
@ -17,7 +20,6 @@ fyang=$dir/scaling.yang
|
|||
fconfig=$dir/large.xml
|
||||
fconfig2=$dir/large2.xml
|
||||
|
||||
format=xml
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module scaling{
|
||||
|
|
@ -59,6 +61,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
|
||||
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
|
||||
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
@ -117,10 +120,11 @@ for mode in startup running; do
|
|||
clixon_util_datastore -d ${mode} -f tree -y $fyang -b $dir -x $tmpx put create
|
||||
;;
|
||||
esac
|
||||
new "Startup backend $format -s $mode -f $cfg -y $fyang"
|
||||
echo "time sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format"
|
||||
new "Startup format: $format mode:$mode"
|
||||
# echo "time sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format"
|
||||
# Cannot use start_backend here due to expected error case
|
||||
time sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format # 2> /dev/null
|
||||
{ time -p sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format 2> /dev/null; } 2>&1 | awk '/real/ {print $2}'
|
||||
|
||||
done
|
||||
done
|
||||
|
||||
|
|
@ -189,7 +193,9 @@ time -p for (( i=0; i<$perfreq; i++ )); do
|
|||
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd > /dev/null
|
||||
done
|
||||
|
||||
# Reference: i686 perfnr=10000 time: 27s 20190425 34s WITH startup copying
|
||||
# Reference:
|
||||
# i686 format=xml perfnr=10000/100 time: 38/29s 20190425 WITH/OUT startup copying
|
||||
# i686 format=tree perfnr=10000/100 time: 72/64s 20190425 WITH/OUT startup copying
|
||||
new "restconf add $perfreq small config"
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_startup.xml
|
||||
|
|
@ -36,6 +39,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
|
||||
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
|
||||
EOF
|
||||
|
|
|
|||
150
test/test_transaction.sh
Executable file
150
test/test_transaction.sh
Executable file
|
|
@ -0,0 +1,150 @@
|
|||
#!/bin/bash
|
||||
# Transaction functionality
|
||||
# The test uses a backend that logs to a file and a netconf client to push
|
||||
# changes. The main example backend plugin logs to the file, and the test
|
||||
# verifies the logs.
|
||||
# The yang is a list with three members, so that you can do add/delete/change
|
||||
# in a single go.
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/trans.yang
|
||||
flog=$dir/backend.log
|
||||
touch $flog
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module trans{
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
container x {
|
||||
list y {
|
||||
key "a";
|
||||
leaf a {
|
||||
type int32;
|
||||
}
|
||||
leaf b {
|
||||
description "change this";
|
||||
type int32;
|
||||
}
|
||||
leaf c {
|
||||
description "del this";
|
||||
type int32;
|
||||
}
|
||||
leaf d {
|
||||
description "add this";
|
||||
type int32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
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_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
|
||||
<CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_DIR>
|
||||
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
<CLICON_SOCK>$dir/$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>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
# Check statements in log
|
||||
checklog(){
|
||||
s=$1 # statement
|
||||
new "Check $s in log"
|
||||
t=$(grep "$s" $flog)
|
||||
if [ -z "$t" ]; then
|
||||
echo -e "\e[31m\nError in Test$testnr [$testname]:"
|
||||
if [ $# -gt 0 ]; then
|
||||
echo "Not found in log"
|
||||
echo
|
||||
fi
|
||||
echo -e "\e[0m"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
new "test params: -f $cfg -l f$flog -- -t"
|
||||
# Bring your own backend
|
||||
if [ $BE -ne 0 ]; then
|
||||
# kill old backend (if any)
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg -l f$flog -- -t"
|
||||
start_backend -s init -f $cfg -l f$flog -- -t # -t means transaction logging
|
||||
new "waiting"
|
||||
sleep $RCWAIT
|
||||
fi
|
||||
|
||||
new "netconf base config (0) a,b,c"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><y><a>0</a><b>0</b><c>0</c></y></x></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit base"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
#Ignore
|
||||
|
||||
new "netconf mixed change: change b, del c, add d"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><y><a>0</a><b>42</b><d>0</d></y></x></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit change"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
# Check complete transaction 2:
|
||||
for op in begin validate complete commit; do
|
||||
checklog "transaction_log 2 $op add: <d>0</d>"
|
||||
checklog "transaction_log 2 $op change: <b>0</b><b>42</b>"
|
||||
done
|
||||
# End is special
|
||||
checklog "transaction_log 2 end add: <d>0</d>"
|
||||
checklog "transaction_log 2 end change: <b>42</b>"
|
||||
|
||||
new "netconf config (1) end-points a,d "
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><y><a>1</a><d>1</d></y></x></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit base"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf insert b,c between end-points"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><y><a>1</a><b>1</b><c>1</c></y></x></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit base"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
checklog "transaction_log 4 validate add: <b>1</b><c>1</c>"
|
||||
|
||||
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
|
||||
|
|
@ -9,12 +9,14 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
|||
|
||||
APPNAME=example
|
||||
|
||||
# Which format to use as datastore format internally
|
||||
: ${format:=xml}
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/type.yang
|
||||
fyang2=$dir/example2.yang
|
||||
fyang3=$dir/example3.yang
|
||||
|
||||
|
||||
# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang
|
||||
cat <<EOF > $fyang3
|
||||
module example3{
|
||||
|
|
@ -214,6 +216,7 @@ testrun(){
|
|||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_CACHE>$dbcache</CLICON_XMLDB_CACHE>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
@ -236,6 +239,10 @@ EOF
|
|||
new "cli set transitive string error. Wrong type"
|
||||
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle 9xx" 255 '^CLI syntax error: "set c talle 9xx": Unknown command$'
|
||||
|
||||
new "netconf discard-changes"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
|
||||
new "netconf set transitive string error"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><c xmlns="urn:example:clixon"><talle>9xx</talle></c></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>"
|
||||
|
||||
|
|
@ -605,10 +612,10 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
# Run with db cache
|
||||
testrun true
|
||||
|
||||
# Run without db cache
|
||||
testrun false
|
||||
|
||||
# Run with db cache
|
||||
testrun true
|
||||
|
||||
rm -rf $dir
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ main(int argc, char **argv)
|
|||
char *x0str = NULL;
|
||||
char *xistr = NULL;
|
||||
char *xpath = NULL;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *yspec = NULL;
|
||||
cxobj *x0 = NULL;
|
||||
cxobj *xb;
|
||||
cxobj *xi = NULL;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ module clixon-config {
|
|||
}
|
||||
enum tree{
|
||||
description "Save and load xmldb as Clixon record-based tree
|
||||
file format";
|
||||
file format (experimental)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -372,7 +372,7 @@ module clixon-config {
|
|||
type boolean;
|
||||
default true;
|
||||
description
|
||||
"XMLDB datastore cache.
|
||||
"XMLDB datatsore cache.
|
||||
If set, XML candidate/running parsed tree is stored in memory
|
||||
If not set, candidate/running is always accessed via disk.";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue