diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57be6bf7..63c5c63f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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: `ietf-netconf:startup`, or use `*:*`
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 358d4c5e..73361f8d 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -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.
*/
diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c
index 3565ea83..6587b581 100644
--- a/apps/backend/backend_commit.c
+++ b/apps/backend/backend_commit.c
@@ -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)
- transaction_free(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)
- transaction_free(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);
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index e132435e..9e663cba 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -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);
diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c
index 367ed627..96195f31 100644
--- a/apps/backend/backend_startup.c
+++ b/apps/backend/backend_startup.c
@@ -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:
diff --git a/apps/backend/clixon_backend_transaction.c b/apps/backend/clixon_backend_transaction.c
index 727a7332..46d4f827 100644
--- a/apps/backend/clixon_backend_transaction.c
+++ b/apps/backend/clixon_backend_transaction.c
@@ -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; itd_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; itd_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; itd_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;
+}
diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h
index 447cd15f..9698b35b 100644
--- a/apps/backend/clixon_backend_transaction.h
+++ b/apps/backend/clixon_backend_transaction.h
@@ -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_ */
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 5c1fa800..a881134b 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -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;
diff --git a/example/main/example_backend.c b/example/main/example_backend.c
index e4df5dd8..fe891335 100644
--- a/example/main/example_backend.c
+++ b/example/main/example_backend.c
@@ -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,
- transaction_data td)
+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:
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index 7d6bc9f6..792099f7 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -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);
diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c
index 278a0023..d5b918c7 100644
--- a/lib/src/clixon_datastore.c
+++ b/lib/src/clixon_datastore.c
@@ -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
#include
#include
@@ -193,7 +197,7 @@ xmldb_copy(clicon_handle h,
db_elmnt de0 = {0,};
cxobj *x1 = NULL; /* from */
cxobj *x2 = NULL; /* to */
-
+
/* XXX lock */
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){
/* Copy in-memory cache */
diff --git a/lib/src/clixon_datastore_tree.c b/lib/src/clixon_datastore_tree.c
index 91c34fdb..6fc61dd8 100644
--- a/lib/src/clixon_datastore_tree.c
+++ b/lib/src/clixon_datastore_tree.c
@@ -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 */
@@ -178,8 +188,8 @@ index_dump(FILE *f,
* @retval 1 OK xp set (or NULL if empty)
*/
static int
-buf2xml(FILE *f,
- uint32_t index,
+buf2xml(FILE *f,
+ uint32_t index,
cxobj **xp)
{
int retval = -1;
@@ -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);
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index ac43a616..e799b166 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -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; iys_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,8 +1332,21 @@ 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)
- cprintf(cb, "/");
+ 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.)
diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c
index a2854d3d..034cb4a7 100644
--- a/lib/src/clixon_xpath.c
+++ b/lib/src/clixon_xpath.c
@@ -715,18 +715,16 @@ 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;
- }
- xr->xc_bool = (strcmp(s1, s2)==0);
+ 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;
- }
- xr->xc_bool = (strcmp(s1, s2));
+ if (s1 == NULL || s2 == NULL)
+ xr->xc_bool = !(s1==NULL && s2 == NULL);
+ else
+ xr->xc_bool = (strcmp(s1, s2));
break;
default:
clicon_err(OE_XML, 0, "Operator %s not supported for nodeset and string", clicon_int2str(xpopmap,op));
diff --git a/test/long.sh b/test/long.sh
index 55191fe7..37d44537 100755
--- a/test/long.sh
+++ b/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 < $cfg
/usr/local/var/$APPNAME/$APPNAME.sock
/usr/local/var/$APPNAME/$APPNAME.pidfile
false
+ $format
/usr/local/var/$APPNAME
false
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 "" > $fconfig
+for (( i=0; i<$perfnr; i++ )); do
+ echo -n "$i$i" >> $fconfig
+done
+echo "]]>]]>" >> $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" "^]]>]]>$"
+
+# 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 "]]>]]>" "^]]>]]>$"
+
+# 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
diff --git a/test/mem.sh b/test/mem.sh
index 94ed4b51..69d4981b 100755
--- a/test/mem.sh
+++ b/test/mem.sh
@@ -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
diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh
index cc457c60..15b16fcd 100755
--- a/test/test_nacm_default.sh
+++ b/test/test_nacm_default.sh
@@ -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 < $cfg
$cfg
@@ -29,6 +32,7 @@ cat < $cfg
/usr/local/lib/xmldb/text.so
false
internal
+ $format
EOF
diff --git a/test/test_netconf.sh b/test/test_netconf.sh
index 3b369875..129cb93a 100755
--- a/test/test_netconf.sh
+++ b/test/test_netconf.sh
@@ -33,7 +33,7 @@ cat < $cfg
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 'eth/0/0ex:ethnone ]]>]]>' '^'
new "Add interface without key"
-expecteof "$clixon_netconf -qf $cfg" 0 'ex:ethnone ]]>]]>' "^]]>]]>$"
-
-new "netconf validate missing key"
-expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory key]]>]]>$'
+expecteof "$clixon_netconf -qf $cfg" 0 'ex:ethnone ]]>]]>' '^applicationmissing-elementnameerrorMandatory key]]>]]>$'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$"
diff --git a/test/test_order.sh b/test/test_order.sh
index 82171613..0408d03e 100755
--- a/test/test_order.sh
+++ b/test/test_order.sh
@@ -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 < $cfg
/usr/local/lib/example/backend
/usr/local/var/$APPNAME/$APPNAME.pidfile
$dbdir
+ $format
EOF
diff --git a/test/test_perf.sh b/test/test_perf.sh
index 75bc0ed5..7adb1ba4 100755
--- a/test/test_perf.sh
+++ b/test/test_perf.sh
@@ -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 < $fyang
module scaling{
@@ -59,6 +61,7 @@ cat < $cfg
1
VARS
0
+ ietf-netconf:startup
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 ) ))
diff --git a/test/test_startup.sh b/test/test_startup.sh
index cd36277c..691c7df7 100755
--- a/test/test_startup.sh
+++ b/test/test_startup.sh
@@ -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 < $cfg
0
init
false
+ $format
EOF
diff --git a/test/test_transaction.sh b/test/test_transaction.sh
new file mode 100755
index 00000000..7eb1f7ad
--- /dev/null
+++ b/test/test_transaction.sh
@@ -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 < $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 < $cfg
+
+ $cfg
+ /usr/local/share/clixon
+ $fyang
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/backend
+ example_backend.so$
+ /usr/local/lib/$APPNAME/netconf
+ /usr/local/lib/$APPNAME/restconf
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ $dir/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ 1
+ /usr/local/var/$APPNAME
+ $format
+
+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 '000]]>]]>' '^]]>]]>$'
+
+new "netconf commit base"
+expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$'
+#Ignore
+
+new "netconf mixed change: change b, del c, add d"
+expecteof "$clixon_netconf -qf $cfg" 0 '0420]]>]]>' '^]]>]]>$'
+
+new "netconf commit change"
+expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$'
+
+# Check complete transaction 2:
+for op in begin validate complete commit; do
+ checklog "transaction_log 2 $op add: 0"
+ checklog "transaction_log 2 $op change: 042"
+done
+# End is special
+checklog "transaction_log 2 end add: 0"
+checklog "transaction_log 2 end change: 42"
+
+new "netconf config (1) end-points a,d "
+expecteof "$clixon_netconf -qf $cfg" 0 '11]]>]]>' '^]]>]]>$'
+
+new "netconf commit base"
+expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$'
+
+new "netconf insert b,c between end-points"
+expecteof "$clixon_netconf -qf $cfg" 0 '111]]>]]>' '^]]>]]>$'
+
+new "netconf commit base"
+expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$'
+
+checklog "transaction_log 4 validate add: 11"
+
+if [ $BE -eq 0 ]; then
+ exit # BE
+fi
+
+new "Kill backend"
+# Check if premature kill
+pid=`pgrep -u root -f clixon_backend`
+if [ -z "$pid" ]; then
+ err "backend already dead"
+fi
+# kill backend
+stop_backend -f $cfg
+
+#rm -rf $dir
diff --git a/test/test_type.sh b/test/test_type.sh
index 1b01d7e8..26d09a53 100755
--- a/test/test_type.sh
+++ b/test/test_type.sh
@@ -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 < $fyang3
module example3{
@@ -214,6 +216,7 @@ testrun(){
1
/usr/local/var/$APPNAME
$dbcache
+ $format
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 "]]>]]>" "^]]>]]>$"
+
+
new "netconf set transitive string error"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '9xx]]>]]>' "^]]>]]>"
@@ -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
diff --git a/util/clixon_util_insert.c b/util/clixon_util_insert.c
index 1edf35f7..0278ada8 100644
--- a/util/clixon_util_insert.c
+++ b/util/clixon_util_insert.c
@@ -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;
diff --git a/yang/clixon/clixon-config@2019-03-05.yang b/yang/clixon/clixon-config@2019-03-05.yang
index d8a1a366..39f535bf 100644
--- a/yang/clixon/clixon-config@2019-03-05.yang
+++ b/yang/clixon/clixon-config@2019-03-05.yang
@@ -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.";
}