diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bd5dd1e..8b5a8f36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,34 @@ Expected: July 2020
### API changes on existing protocol/config features (For users)
+* Top-level default leafs assigned.
+ * Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values.
+ * In this process non-presence containers may be created.
+ * See also [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
+* NACM default behaviour is read-only (empty configs are dead-lockedd)
+ * This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal`
+ * Due to the previous bult (top-level default leafs)
+ * This means that empty configs or empty NACM configs are not writable (deadlocked).
+ * Workarounds:
+ 1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
+
+ * This means that empty configs or empty NACM configs are not writable (deadlocked).
+ * Workarounds:
+ 1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
+
+ * This means that empty configs or empty NACM configs are not writable (deadlocked).
+ * Workarounds:
+ 1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
+
+ * This means that empty configs or empty NACM configs are not writable (deadlocked).
+ * Workarounds:
+ 1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
+
+ * This means that empty configs or empty NACM configs are not writable (deadlocked).
+ * Workarounds:
+ 1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
+ 2. Edit the startup-db with a valid NACM config and restart the system
+ 3. Set clixon option CLICON_NACM_DISABLED_ON_EMPTY to true which means that if the config is (completely) empty, you can add a NACM config as a first edit.
* Netconf lock/unlock behaviour changed to adhere to RFC 6241
* Changed commit lock error tag from "lock denied" to "in-use".
* Changed unlock error message from "lock is already held" to #lock not active" or "lock held by other session".
@@ -53,6 +81,7 @@ Expected: July 2020
* CLICON_SSL_SERVER_CERT
* CLICON_SSL_SERVER_KEY
* CLICON_SSL_CA_CERT
+ * Added CLICON_NACM_DISABLED_ON_EMPTY to mitigate read-only "dead-lock" of empty startup configs.
* Restconf FCGI (eg via nginx) have changed reply message syntax slightly as follows (due to refactoring and common code with evhtp):
* Bodies in error retuns including html code have been removed
* Some (extra) CRLF:s have been removed
@@ -99,6 +128,8 @@ Expected: July 2020
### Corrected Bugs
+* Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
+ * See also API changes since this changes NACM behavior for example.
* Fixed: Don't call upgrade callbacks if no revision defined so there's no way to determine right way 'from' and 'to'
* Fixed: [lock candidate succeeded even though it is modified #110](https://github.com/clicon/clixon/issues/110)
* Fixed: [Need to add the possibility to use anchors around patterns #51](https://github.com/clicon/cligen/issues/51):
diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c
index 52be4f30..21f2a579 100644
--- a/apps/backend/backend_startup.c
+++ b/apps/backend/backend_startup.c
@@ -226,7 +226,7 @@ startup_extraxml(clicon_handle h,
/* Clear tmp db */
if (xmldb_db_reset(h, tmp_db) < 0)
goto done;
- /* Application may define extra xml in its reset function*/
+ /* Application may define extra xml in its reset function */
if (clixon_plugin_reset_all(h, tmp_db) < 0)
goto done;
/* Extra XML can also be added via file */
@@ -238,13 +238,13 @@ startup_extraxml(clicon_handle h,
goto fail;
}
/*
- * Check if tmp db is empty.
+ * Check if tmp db is empty. XXX no this is not possible.
* It should be empty if extra-xml is null and reset plugins did nothing
* then skip validation.
*/
if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0)
goto done;
- if (xt0==NULL || xml_child_nr(xt0)==0)
+ if (xmldb_empty_get(h, tmp_db))
goto ok;
xt = NULL;
/* Validate the tmp db and return possibly upgraded xml in xt
diff --git a/lib/clixon/clixon_data.h b/lib/clixon/clixon_data.h
index bf46003d..79641950 100644
--- a/lib/clixon/clixon_data.h
+++ b/lib/clixon/clixon_data.h
@@ -54,7 +54,8 @@
typedef struct {
uint32_t de_id; /* session id */
cxobj *de_xml; /* cache */
- int de_modified;
+ int de_modified; /* Dirty since loaded/copied/committed/etc XXX:nocache? */
+ int de_empty; /* Empty on read from file, xmldb_readfile and xmldb_put sets it */
} db_elmnt;
/*
diff --git a/lib/clixon/clixon_datastore.h b/lib/clixon/clixon_datastore.h
index c0bacc7f..30bbf4e3 100644
--- a/lib/clixon/clixon_datastore.h
+++ b/lib/clixon/clixon_datastore.h
@@ -73,7 +73,7 @@ cxobj *xmldb_cache_get(clicon_handle h, const char *db);
int xmldb_modified_get(clicon_handle h, const char *db);
int xmldb_modified_set(clicon_handle h, const char *db, int value);
-
+int xmldb_empty_get(clicon_handle h, const char *db);
int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt);
#endif /* _CLIXON_DATASTORE_H */
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index ad404a7a..15adc657 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -66,6 +66,9 @@ int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_default(cxobj *x);
int xml_default_recurse(cxobj *xn);
+int xml_default_yspec(yang_stmt *yspec, cxobj *xn);
+int xml_nopresence_default(cxobj *xt);
+int xml_nopresence_default_mark(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, void *arg);
diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h
index 583500e4..11a91971 100644
--- a/lib/clixon/clixon_yang_module.h
+++ b/lib/clixon/clixon_yang_module.h
@@ -49,12 +49,13 @@
/* Struct containing module state differences between two modules or two
* revisions of same module.
- * This is in state of flux so it needs to be contained and easily changed.
+ * The most significant usecase is one module-state is a loaded datastore and the other
+ * is the one loaded by the server by its YANG files.
*/
typedef struct {
int md_status; /* 0 if no module-state in a datastore, 1 if there is */
char *md_set_id; /* server-specific identifier */
- cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
+ cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
} modstate_diff_t;
/*
diff --git a/lib/src/clixon_data.c b/lib/src/clixon_data.c
index cada69b6..e46b2b6e 100644
--- a/lib/src/clixon_data.c
+++ b/lib/src/clixon_data.c
@@ -636,12 +636,11 @@ clicon_argv_set(clicon_handle h,
return retval;
}
-/*! Get xml database element including pid and xml cache
+/*! Get xml database element including id, xml cache, empty on startup and dirty bit
* @param[in] h Clicon handle
* @param[in] db Name of database
* @retval de Database element
* @retval NULL None found
- * @note these use db_elmnt hash, not data
*/
db_elmnt *
clicon_db_elmnt_get(clicon_handle h,
@@ -655,14 +654,12 @@ clicon_db_elmnt_get(clicon_handle h,
return NULL;
}
-/*! Set xml database element including pid and xml cache
+/*! Set xml database element including id, xml cache, empty on startup and dirty bit
* @param[in] h Clicon handle
* @param[in] db Name of database
* @param[in] de Database element
* @retval 0 OK
* @retval -1 Error
- * XXX add prefix to db to ensure uniqueness?
- * @note these use db_elmnt hash, not data
*/
int
clicon_db_elmnt_set(clicon_handle h,
diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c
index b091c08d..be771457 100644
--- a/lib/src/clixon_datastore.c
+++ b/lib/src/clixon_datastore.c
@@ -518,6 +518,26 @@ xmldb_modified_get(clicon_handle h,
return de->de_modified;
}
+/*! Get empty flag from datastore (the datastore was empty ON LOAD)
+ * @param[in] h Clicon handle
+ * @param[in] db Database name
+ * @retval -1 Error (datastore does not exist)
+ * @retval 0 Db was not empty on load
+ * @retval 1 Db was empty on load
+ */
+int
+xmldb_empty_get(clicon_handle h,
+ const char *db)
+{
+ db_elmnt *de;
+
+ if ((de = clicon_db_elmnt_get(h, db)) == NULL){
+ clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
+ return -1;
+ }
+ return de->de_empty;
+}
+
/*! Get modified flag from datastore
* @param[in] h Clicon handle
* @param[in] db Database name
diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c
index ac0acd9f..bcb686d2 100644
--- a/lib/src/clixon_datastore_read.c
+++ b/lib/src/clixon_datastore_read.c
@@ -72,6 +72,7 @@
#include "clixon_xpath.h"
#include "clixon_json.h"
#include "clixon_nacm.h"
+#include "clixon_path.h"
#include "clixon_netconf_lib.h"
#include "clixon_yang_module.h"
#include "clixon_xml_map.h"
@@ -104,7 +105,7 @@ singleconfigroot(cxobj *xt,
}
}
if (i != 1){
- clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
+ clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
goto done;
}
x = NULL;
@@ -443,33 +444,63 @@ text_read_modstate(clicon_handle h,
return retval;
}
+static int
+disable_nacm_on_empty(cxobj *x0,
+ yang_stmt *yspec)
+{
+ int retval = -1;
+ yang_stmt *ymod;
+ cxobj **vec = NULL;
+ int len = 0;
+ cxobj *xb;
+
+ if ((ymod = yang_find(yspec, Y_MODULE, "ietf-netconf-acm")) == NULL)
+ goto ok;
+ if (clixon_xml_find_instance_id(x0, yspec, &vec, &len, "/nacm:nacm/nacm:enable-nacm") < 1)
+ goto done;
+ if (len){
+ if ((xb = xml_body_get(vec[0])) == NULL)
+ goto done;
+ if (xml_value_set(xb, "false") < 0)
+ goto done;
+ }
+ if (vec)
+ free(vec);
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
/*! Common read function that reads an XML tree from file
* @param[in] th Datastore text handle
* @param[in] db Symbolic database name, eg "candidate", "running"
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Top-level yang spec
* @param[out] xp XML tree read from file
+ * @param[out] de If set, return db-element status (eg empty flag)
* @param[out] msdiff If set, return modules-state differences
* @retval -1 Error
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval 1 OK
- * @note retval 0 is NYI becaues of functions calling this function
+ * @note retval 0 is NYI because of functions calling this function cannot handle it yet
*/
-#undef XMLDB_READFILE_FAIL
+#undef XMLDB_READFILE_FAIL /* See comment on retval = 0 above */
int
xmldb_readfile(clicon_handle h,
const char *db,
yang_bind yb,
yang_stmt *yspec,
cxobj **xp,
+ db_elmnt *de,
modstate_diff_t *msdiff)
{
- int retval = -1;
- cxobj *x0 = NULL;
- char *dbfile = NULL;
- int fd = -1;
- char *format;
- int ret;
+ int retval = -1;
+ cxobj *x0 = NULL;
+ char *dbfile = NULL;
+ int fd = -1;
+ char *format;
+ int ret;
if (xmldb_db2file(h, db, &dbfile) < 0)
goto done;
@@ -497,8 +528,9 @@ xmldb_readfile(clicon_handle h,
goto fail;
#endif
/* Always assert a top-level called "config".
- To ensure that, deal with two cases:
- 1. File is empty -> rename top-level to "config" */
+ * To ensure that, deal with two cases:
+ * 1. File is empty -> rename top-level to "config"
+ */
if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0)
goto done;
@@ -509,7 +541,10 @@ xmldb_readfile(clicon_handle h,
if (singleconfigroot(x0, &x0) < 0)
goto done;
}
- /* From Clixon 3.10,datastore files may contain module-state defining
+ if (xml_child_nr(x0) == 0 && de)
+ de->de_empty = 1;
+
+ /* Datastore files may contain module-state defining
* which modules are used in the file.
*/
if (text_read_modstate(h, yspec, x0, msdiff) < 0)
@@ -569,16 +604,36 @@ xmldb_get_nocache(clicon_handle h,
size_t xlen;
int i;
int ret;
+ db_elmnt de0 = {0,};
+ int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
- if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, msdiff)) < 0)
+ if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
+ clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
+ /* Check if empty */
+ if (xml_child_nr(xt) == 0)
+ empty = 1;
+ /* Add global defaults.
+ * Must do it before xpath check, since globals may be filtered out
+ */
+ if (xml_default_yspec(yspec, xt) < 0)
+ goto done;
+ /* If empty database, then disable NACM if loaded
+ * This has some drawbacks. One is that a config may become empty at a later stage
+ * and then this does not hold.
+ */
+ if (empty &&
+ clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
+ if (disable_nacm_on_empty(xt, yspec) < 0)
+ goto done;
+ }
/* Here xt looks like: ... */
/* Given the xpath, return a vector of matches in xvec */
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
@@ -664,6 +719,7 @@ xmldb_get_cache(clicon_handle h,
cxobj *x1t = NULL;
db_elmnt de0 = {0,};
int ret;
+ int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
@@ -672,7 +728,7 @@ xmldb_get_cache(clicon_handle h,
de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
/* If there is no xml x0 tree (in cache), then read it from file */
- if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
+ if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
@@ -680,10 +736,30 @@ xmldb_get_cache(clicon_handle h,
* No, argument against: we may want to have a semantically wrong file and wish to edit?
*/
de0.de_xml = x0t;
- clicon_db_elmnt_set(h, db, &de0);
+ clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
} /* x0t == NULL */
else
x0t = de->de_xml;
+
+ /* Check if empty, must be before add global defaults */
+ if (xml_child_nr(x0t) == 0)
+ empty = 1;
+
+ /* Add global defaults.
+ * Cant do it to x1t since that is after xpath check, since globals may be filtered out
+ */
+ if (xml_default_yspec(yspec, x0t) < 0)
+ goto done;
+ /* If empty database, then disable NACM if loaded
+ * This has some drawbacks. One is that a config may become empty at a later stage
+ * and then this does not hold.
+ */
+ if (empty &&
+ clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
+ if (disable_nacm_on_empty(x0t, yspec) < 0)
+ goto done;
+ }
+
/* Here x0t looks like: ... */
/* Given the xpath, return a vector of matches in xvec
* Can we do everything in one go?
@@ -693,8 +769,6 @@ xmldb_get_cache(clicon_handle h,
* a) for every node that is found, copy to new tree
* b) if config dont dont state data
*/
-
- /* Here xt looks like: ... */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@@ -703,6 +777,7 @@ xmldb_get_cache(clicon_handle h,
goto done;
xml_spec_set(x1t, xml_spec(x0t));
+
if (xlen < 1000){
/* This is optimized for the case when the tree is large and xlen is small
* If the tree is large and xlen too, then the other is better.
@@ -786,6 +861,7 @@ xmldb_get_zerocopy(clicon_handle h,
db_elmnt *de = NULL;
db_elmnt de0 = {0,};
int ret;
+ int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
@@ -794,7 +870,7 @@ xmldb_get_zerocopy(clicon_handle h,
de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
/* If there is no xml x0 tree (in cache), then read it from file */
- if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
+ if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
@@ -806,6 +882,25 @@ xmldb_get_zerocopy(clicon_handle h,
} /* x0t == NULL */
else
x0t = de->de_xml;
+ /* Check if empty, must be before add global defaults */
+ if (xml_child_nr(x0t) == 0)
+ empty = 1;
+
+ /* Add global defaults.
+ * Cant do it to x1t since that is after xpath check, since globals may be filtered out
+ */
+ if (xml_default_yspec(yspec, x0t) < 0)
+ goto done;
+ /* If empty database, then disable NACM if loaded
+ * This has some drawbacks. One is that a config may become empty at a later stage
+ * and then this does not hold.
+ */
+ if (empty &&
+ clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
+ if (disable_nacm_on_empty(x0t, yspec) < 0)
+ goto done;
+ }
+
/* Here xt looks like: ... */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@@ -874,7 +969,7 @@ xmldb_get(clicon_handle h,
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache
* @param[out] xret Single return XML tree. Free with xml_free()
- * @param[out] msdiff If set, return modules-state differences (upgrade code)
+ * @param[out] msdiff If set, return modules-state differences (upgrade code)
* @retval 0 OK
* @retval -1 Error
* @code
@@ -944,12 +1039,16 @@ xmldb_get0_clear(clicon_handle h,
if (x == NULL)
goto ok;
- /* clear XML tree of defaults */
+ /* Mark non-presence containers as XML_FLAG_DEFAULT */
+ if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
+ goto done;
+ /* Clear XML tree of defaults */
if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
goto done;
+
/* clear mark and change */
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
- (void*)(0xff));
+ (void*)(0xffff));
ok:
retval = 0;
done:
@@ -972,3 +1071,4 @@ xmldb_get0_free(clicon_handle h,
*xp = NULL;
return 0;
}
+
diff --git a/lib/src/clixon_datastore_read.h b/lib/src/clixon_datastore_read.h
index 3c87fe37..da182e57 100644
--- a/lib/src/clixon_datastore_read.h
+++ b/lib/src/clixon_datastore_read.h
@@ -39,6 +39,7 @@
/*
* Prototypes
*/
-int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec, cxobj **xp, modstate_diff_t *msd);
+int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec,
+ cxobj **xp, db_elmnt *de, modstate_diff_t *msd);
#endif /* _CLIXON_DATASTORE_READ_H */
diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c
index 1044096b..ba1e45aa 100644
--- a/lib/src/clixon_datastore_write.c
+++ b/lib/src/clixon_datastore_write.c
@@ -481,9 +481,11 @@ text_modify(clicon_handle h,
switch(op){
case OP_CREATE:
if (x0){
- if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
- goto done;
- goto fail;
+ if (xml_nopresence_default(x0) == 0){
+ if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
+ goto done;
+ goto fail;
+ }
}
case OP_REPLACE: /* fall thru */
case OP_MERGE:
@@ -928,7 +930,7 @@ xmldb_put(clicon_handle h,
/* If there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){
firsttime++; /* to avoid leakage on error, see fail from text_modify */
- if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, NULL)) < 0)
+ if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, de, NULL)) < 0)
goto done;
if (ret == 0)
goto fail;
@@ -987,10 +989,10 @@ xmldb_put(clicon_handle h,
db_elmnt de0 = {0,};
if (de != NULL)
de0 = *de;
- if (de0.de_xml == NULL){
+ if (de0.de_xml == NULL)
de0.de_xml = x0;
- clicon_db_elmnt_set(h, db, &de0);
- }
+ de0.de_empty = (xml_child_nr(de0.de_xml) == 0);
+ clicon_db_elmnt_set(h, db, &de0);
}
if (xmldb_db2file(h, db, &dbfile) < 0)
goto done;
@@ -1057,6 +1059,9 @@ xmldb_dump(clicon_handle h,
char *format;
int pretty;
+ /* clear XML tree of defaults */
+ if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
+ goto done;
/* Add modstate first */
if ((x = clicon_modst_cache_get(h, 1)) != NULL){
if ((xmodst = xml_dup(x)) == NULL)
@@ -1079,3 +1084,4 @@ xmldb_dump(clicon_handle h,
done:
return retval;
}
+
diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c
index fb5fab70..b9fc32fa 100644
--- a/lib/src/clixon_nacm.c
+++ b/lib/src/clixon_nacm.c
@@ -1100,7 +1100,9 @@ nacm_access(clicon_handle h,
/* Do initial nacm processing common to all access validation in
* RFC8341 3.4 */
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
- operation is permitted. */
+ * operation is permitted.
+ * note option CLICON_NACM_DISABLED_ON_EMPTY
+ */
if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
goto permit;
enabled = xml_body(x);
@@ -1155,6 +1157,7 @@ nacm_access_pre(clicon_handle h,
cxobj *xnacm = NULL;
cvec *nsc = NULL;
+ /* Check clixon option: disabled, external tree or internal */
mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode == NULL)
goto permit;
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 712b32d0..f4333a5a 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -1883,6 +1883,7 @@ xml_copy_one(cxobj *x0,
default:
break;
}
+ xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT)); /* Maybe more flags */
retval = 0;
done:
return retval;
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 8e7e6461..1e976217 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -979,9 +979,223 @@ xml_namespace_change(cxobj *x,
return retval;
}
-/*! Add default values (if not set)
- * @param[in] xt XML tree with some node marked
- * Typically called in a recursive apply function:
+int
+xml_default_create1(yang_stmt *y,
+ cxobj *xt,
+ int top,
+ cxobj **xcp)
+{
+ int retval = -1;
+ char *namespace;
+ char *prefix;
+ int ret;
+ cxobj *xc = NULL;
+
+ if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
+ goto done;
+ xml_spec_set(xc, y);
+
+ /* assign right prefix */
+ if ((namespace = yang_find_mynamespace(y)) != NULL){
+ prefix = NULL;
+ if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
+ goto done;
+ if (ret){ /* Namespace found, prefix returned in prefix */
+ if (xml_prefix_set(xc, prefix) < 0)
+ goto done;
+ }
+ else{ /* Namespace does not exist in target, must add it w xmlns attr.
+ use source prefix */
+ if (!top){
+ if ((prefix = yang_find_myprefix(y)) == NULL){
+ clicon_err(OE_UNIX, errno, "strdup");
+ goto done;
+ }
+ }
+ if (add_namespace(xc, xc, prefix, namespace) < 0)
+ goto done;
+ /* Add prefix to x, if any */
+ if (prefix && xml_prefix_set(xc, prefix) < 0)
+ goto done;
+ }
+ }
+ if (xml_addsub(xt, xc) < 0)
+ goto done;
+ *xcp = xc;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Create leaf from default value
+ *
+ * @param[in] yt Yang spec
+ * @param[in] xt XML tree
+ * @param[in] top Use default namespace (if you create xmlns statement)
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+xml_default_create(yang_stmt *y,
+ cxobj *xt,
+ int top)
+{
+ int retval = -1;
+ cxobj *xc = NULL;
+ cxobj *xb;
+ char *str;
+
+ if (xml_default_create1(y, xt, top, &xc) < 0)
+ goto done;
+ xml_flag_set(xc, XML_FLAG_DEFAULT);
+ if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
+ goto done;
+ if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
+ clicon_err(OE_UNIX, errno, "cv2str_dup");
+ goto done;
+ }
+ if (xml_value_set(xb, str) < 0)
+ goto done;
+ free(str);
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Try to see if intermediate nodes are necessary for default values, create if so
+ *
+ * @param[in] yt Yang container (no-presence)
+ * @param[out] createp Need to create XML container
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+xml_nopresence_try(yang_stmt *yt,
+ int *createp)
+{
+ int retval = -1;
+ yang_stmt *y;
+
+ if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
+ clicon_err(OE_XML, EINVAL, "yt argument is not container");
+ goto done;
+ }
+ *createp = 0;
+ y = NULL;
+ while ((y = yn_each(yt, y)) != NULL) {
+ switch (yang_keyword_get(y)){
+ case Y_LEAF:
+ if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
+ /* Need to create container */
+ *createp = 1;
+ goto ok;
+ }
+ break;
+ case Y_CONTAINER:
+ if (yang_find(y, Y_PRESENCE, NULL) == NULL){
+ /* If this is non-presence, (and it does not exist in xt) call recursively
+ * and create nodes if any default value exist first. Then continue and populate?
+ */
+ if (xml_nopresence_try(y, createp) < 0)
+ goto done;
+ if (*createp)
+ goto ok;
+ }
+ break;
+ default:
+ break;
+ } /* switch */
+ }
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Ensure default values are set on (children of) one single xml node
+ *
+ * Not recursive, except in one case with one or several non-presence containers, in which case
+ * XML containers may be created to host default values. That code may be a little too recursive.
+ * @param[in] yt Yang spec
+ * @param[in] xt XML tree (with yt as spec of xt, informally)
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+xml_default1(yang_stmt *yt,
+ cxobj *xt)
+{
+ int retval = -1;
+ yang_stmt *yc;
+ cxobj *xc;
+ int top=0; /* Top symbol (set default namespace) */
+ int create = 0;
+
+ if (xt == NULL){ /* No xml */
+ clicon_err(OE_XML, EINVAL, "No XML argument");
+ goto done;
+ }
+ switch (yang_keyword_get(yt)){
+ case Y_MODULE:
+ case Y_SUBMODULE:
+ top++;
+ case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
+ case Y_LIST:
+ case Y_INPUT:
+ case Y_OUTPUT:
+ yc = NULL;
+ while ((yc = yn_each(yt, yc)) != NULL) {
+ switch (yang_keyword_get(yc)){
+ case Y_LEAF:
+ if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
+ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
+ /* No such child exist, create this leaf */
+ if (xml_default_create(yc, xt, top) < 0)
+ goto done;
+ xml_sort(xt);
+ }
+ }
+ break;
+ case Y_CONTAINER:
+ if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
+ /* If this is non-presence, (and it does not exist in xt) call
+ * recursively and create nodes if any default value exist first.
+ * Then continue and populate?
+ */
+ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
+ /* No such container exist, recursively try if needed */
+ if (xml_nopresence_try(yc, &create) < 0)
+ goto done;
+ if (create){
+ /* Retval shows there is a default value need to create the
+ * container */
+ if (xml_default_create1(yc, xt, top, &xc) < 0)
+ goto done;
+ xml_sort(xt);
+ /* Then call it recursively */
+ if (xml_default1(yc, xc) < 0)
+ goto done;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ } /* switch */
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Ensure default values are set on existing leaf children of this node
+ *
+ * Assume yang is bound to the tree
+ * @param[in] xt XML tree
* @retval 0 OK
* @retval -1 Error
*/
@@ -990,79 +1204,22 @@ xml_default(cxobj *xt)
{
int retval = -1;
yang_stmt *ys;
- yang_stmt *y;
- // int i; // XXX
- cxobj *xc;
- cxobj *xb;
- char *str;
- int added=0;
- char *namespace;
- char *prefix;
- int ret;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
- /* Check leaf defaults */
- if (yang_keyword_get(ys) == Y_CONTAINER || yang_keyword_get(ys) == Y_LIST ||
- yang_keyword_get(ys) == Y_INPUT){
- y = NULL;
- while ((y = yn_each(ys, y)) != NULL) {
- if (yang_keyword_get(y) != Y_LEAF)
- continue;
- if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
- if (!xml_find(xt, yang_argument_get(y))){
- if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
- goto done;
- xml_spec_set(xc, y);
-
- /* assign right prefix */
- if ((namespace = yang_find_mynamespace(y)) != NULL){
- prefix = NULL;
- if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
- goto done;
- if (ret){
- if (xml_prefix_set(xc, prefix) < 0)
- goto done;
- }
- else{ /* namespace does not exist in target, use source prefix */
- if ((prefix = yang_find_myprefix(y)) == NULL){
- clicon_err(OE_UNIX, errno, "strdup");
- goto done;
- }
- if (add_namespace(xc, xc, prefix, namespace) < 0)
- goto done;
- /* Add prefix to x, if any */
- if (prefix && xml_prefix_set(xc, prefix) < 0)
- goto done;
- }
- }
-
- xml_flag_set(xc, XML_FLAG_DEFAULT);
- if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
- goto done;
- if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
- clicon_err(OE_UNIX, errno, "cv2str_dup");
- goto done;
- }
- if (xml_value_set(xb, str) < 0)
- goto done;
- free(str);
- added++;
- if (xml_insert(xt, xc, INS_LAST, NULL, NULL) < 0)
- goto done;
- }
- }
- }
- }
+ if (xml_default1(ys, xt) < 0)
+ goto done;
retval = 0;
done:
return retval;
}
-/*! Recursively fill in default values in a tree
- * Alt to use xml_apply
+/*! Recursively fill in default values in an XML tree
+ * @param[in] xt XML tree
+ * @retval 0 OK
+ * @retval -1 Error
*/
int
xml_default_recurse(cxobj *xn)
@@ -1082,6 +1239,85 @@ xml_default_recurse(cxobj *xn)
return retval;
}
+/*! Ensure default values are set on top-level
+ *
+ * Not recursive, except in one case with one or several non-presence containers
+ * @param[in] xt XML tree
+ * Typically called in a recursive apply function:
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+xml_default_yspec(yang_stmt *yspec,
+ cxobj *xt)
+{
+ int retval = -1;
+ yang_stmt *ymod = NULL;
+
+ if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
+ clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
+ goto done;
+ }
+ while ((ymod = yn_each(yspec, ymod)) != NULL)
+ if (xml_default1(ymod, xt) < 0)
+ goto done;
+
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! This node is a default set value or (recursively) a non-presence container
+ * @retval 1 xt is a nopresence/default node (ie "virtual")
+ * @retval 0 xt is not such a node
+ */
+int
+xml_nopresence_default(cxobj *xt)
+{
+ cxobj *xc;
+ yang_stmt *yt;
+
+ if ((yt = xml_spec(xt)) == NULL)
+ return 0;
+ switch (yang_keyword_get(yt)){
+ case Y_CONTAINER:
+ if (yang_find(yt, Y_PRESENCE, NULL))
+ return 0;
+ break;
+ case Y_LEAF:
+ return xml_flag(xt, XML_FLAG_DEFAULT)?1:0;
+ break;
+ default:
+ return 0;
+ }
+ xc = NULL;
+ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
+ if (xml_nopresence_default(xc) == 0)
+ return 0;
+ }
+ return 1;
+}
+
+/*! Remove xml container if it is non-presence and only contains default leafs
+ * Called from xml_apply. Reason for marking is to delete it afterwords.
+ * @param[in] x
+ * @param[in] arg (flag value)
+ * @code
+ * if (xml_apply(xt, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
+ * err;
+ * if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
+ * goto done;
+ * @endcode
+ */
+int
+xml_nopresence_default_mark(cxobj *x,
+ void *arg)
+{
+ if (xml_nopresence_default(x))
+ xml_flag_set(x, (intptr_t)arg);
+ return 0;
+}
+
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index 87289a69..c3182e82 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -1029,6 +1029,7 @@ xml_insert2(cxobj *xp,
* @retval 0 OK
* @retval -1 Error
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
+ * @note It is assumed that all siblings of xi are YANG bound
*/
int
xml_insert(cxobj *xp,
diff --git a/test/test_nacm.sh b/test/test_nacm.sh
index 23a8c041..540f2e49 100755
--- a/test/test_nacm.sh
+++ b/test/test_nacm.sh
@@ -2,7 +2,9 @@
# Authentication and authorization and IETF NACM
# See RFC 8341 A.2
# But replaced ietf-netconf-monitoring with *
-# Note credenials check set to none since USER poses as different users.
+# Note:
+# 1. credenials check set to none since USER poses as different users.
+# 2. CLICON_NACM_DISABLE_ON_EMPTY: start with empty config and add nacm config
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@@ -31,6 +33,7 @@ cat < $cfg
/usr/local/var/$APPNAME
false
internal
+ true
EOF
@@ -140,7 +143,7 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/da
# explicitly disable nacm (regression on netgate bug)
new "disable nacm"
-expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 201 Created"
+expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content"
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$"
diff --git a/test/test_nacm_credentials.sh b/test/test_nacm_credentials.sh
index c470a380..b0b0189f 100755
--- a/test/test_nacm_credentials.sh
+++ b/test/test_nacm_credentials.sh
@@ -123,6 +123,7 @@ cat < $cfg
/usr/local/var/$APPNAME/$APPNAME.pidfile
/usr/local/var/$APPNAME
internal
+ true
$mode
EOF
diff --git a/test/test_nacm_datanode.sh b/test/test_nacm_datanode.sh
index ea680008..7a90b44c 100755
--- a/test/test_nacm_datanode.sh
+++ b/test/test_nacm_datanode.sh
@@ -62,6 +62,7 @@ cat < $cfg
/usr/local/var/$APPNAME
false
internal
+ true
EOF
diff --git a/test/test_nacm_datanode_read.sh b/test/test_nacm_datanode_read.sh
index 98332ac1..3b9e915c 100755
--- a/test/test_nacm_datanode_read.sh
+++ b/test/test_nacm_datanode_read.sh
@@ -42,6 +42,7 @@ cat < $cfg
/usr/local/var/$APPNAME
false
internal
+ true
EOF
diff --git a/test/test_nacm_datanode_write.sh b/test/test_nacm_datanode_write.sh
index 83fb5efb..9d0cd2a6 100755
--- a/test/test_nacm_datanode_write.sh
+++ b/test/test_nacm_datanode_write.sh
@@ -34,6 +34,7 @@ cat < $cfg
/usr/local/var/$APPNAME
false
internal
+ true
EOF
diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh
index f56c5db4..88b485c4 100755
--- a/test/test_nacm_default.sh
+++ b/test/test_nacm_default.sh
@@ -30,6 +30,7 @@ cat < $cfg
$dir
false
internal
+ true
$format
EOF
@@ -62,7 +63,6 @@ EOF
# 6: expected return value of test2
# 7: expected return value of test3
# 8: startup mode: startup or init
-# 9: Dont set default values (nullify them)
testrun(){
enablenacm=$1
readdefault=$2
@@ -72,24 +72,9 @@ testrun(){
ret2=$6
ret3=$7
db=$8
- nulldef=$9
- # Set default values (or not)
- if [ $nulldef -ne 0 ]; then
- # Defaults should be: true permit deny permit:
- # nacm enabled, exec default permit, read permit (expect fail)"
- # which means results should be 0 1 3
- # Also enable-nacm is present since otherwise the nacm container would be removed
- # since it is non-presence
- NACM=$(cat <
- ${enablenacm}
-
-EOF
-)
- else
- NACM=$(cat <
+ NACM=$(cat <
${enablenacm}
${readdefault}
${writedefault}
@@ -98,7 +83,6 @@ EOF
EOF
)
- fi
# Initial data
XML='42'
@@ -117,6 +101,7 @@ EOF
start_backend -s $db -f $cfg
else
new "Restart backend as eg follows: -Ff $cfg -s $db"
+ sleep 2
fi
new "waiting"
@@ -132,12 +117,16 @@ EOF
wait_restconf
# Use POST (instead of startup)
+ # Note this only works because CLICON_NACM_DISABLED_ON_EMPTY is true
if [ $db = init ]; then
- new "Set Initial data using POST"
- expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
+ # Must set NACM first
+ new "Set NACM using PATCH"
+ expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" -d "$NACM$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
+
+# new "Set Initial data using POST"
+# expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
- new "Set NACM using POST"
- expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
+
fi
#----------- First get
@@ -204,39 +193,36 @@ EOF
# Run a lot of tests with different settings of default read/write/exec
# Outer loop either starts from startup or inits config via restconf POST
for db in startup init; do
- new "nacm enabled and all defaults permit"
- testrun true permit permit permit 0 0 0 $db 0
+ new "1. nacm enabled and all defaults permit"
+ testrun true permit permit permit 0 0 0 $db
- new "nacm disabled and all defaults permit"
- testrun false permit permit permit 0 0 0 $db 0
+ new "2. nacm disabled and all defaults permit"
+ testrun false permit permit permit 0 0 0 $db
- new "nacm disabled and all defaults deny"
- testrun false deny deny deny 0 0 0 $db 0
+ new "3. nacm disabled and all defaults deny"
+ testrun false deny deny deny 0 0 0 $db
- new "nacm enabled, all defaults deny (expect fail)"
- testrun true deny deny deny 1 1 1 $db 0
+ new "4. nacm enabled, all defaults deny (expect fail)"
+ testrun true deny deny deny 1 1 1 $db
- new "nacm enabled, exec default deny - read permit (expect fail)"
- testrun true permit deny deny 1 1 1 $db 0
+ new "5. nacm enabled, exec default deny - read permit (expect fail)"
+ testrun true permit deny deny 1 1 1 $db
- new "nacm enabled, exec default deny - write permit (expect fail)"
- testrun true deny permit deny 1 1 1 $db 0
+ new "6. nacm enabled, exec default deny - write permit (expect fail)"
+ testrun true deny permit deny 1 1 1 $db
- new "nacm enabled, exec default deny read/write permit (expect fail)"
- testrun true permit permit deny 1 1 1 $db 0
+ new "7. nacm enabled, exec default deny read/write permit (expect fail)"
+ testrun true permit permit deny 1 1 1 $db
- new "nacm enabled, exec default permit, all others deny (expect fail)"
- testrun true deny deny permit 2 1 2 $db 0
+ new "8. nacm enabled, exec default permit, all others deny (expect fail)"
+ testrun true deny deny permit 2 1 2 $db
- new "nacm enabled, exec default permit, read permit (expect fail)"
- testrun true permit deny permit 0 1 3 $db 0 # This is yang default
+ new "9. nacm enabled, exec default permit, read permit (expect fail)"
+ testrun true permit deny permit 0 1 3 $db # This is yang default
- new "nacm enabled, with default values (no settings - should be same as previous)"
- # note last 1 means nullify all default values)
- testrun true xxx xxx xxx 0 1 3 init 1
+ new "10. nacm enabled, exec default permit, write permit (expect fail)"
+ testrun true deny permit permit 2 0 2 $db
- new "nacm enabled, exec default permit, write permit (expect fail)"
- testrun true deny permit permit 2 0 2 $db 0
done
rm -rf $dir
diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh
index a5606c60..19c5a60c 100755
--- a/test/test_nacm_ext.sh
+++ b/test/test_nacm_ext.sh
@@ -48,7 +48,8 @@ module nacm-example{
}
prefix nacm;
container authentication {
- description "Example code for enabling www basic auth and some example
+ presence "To keep this from auto-expanding";
+ description "Example code for enabling www basic auth and some example
users";
leaf basic_auth{
description "Basic user / password authentication as in HTTP basic auth";
diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh
index da9c888e..4d7a9f2b 100755
--- a/test/test_nacm_module_read.sh
+++ b/test/test_nacm_module_read.sh
@@ -35,6 +35,7 @@ cat < $cfg
false
internal
none
+ true
EOF
diff --git a/test/test_nacm_module_write.sh b/test/test_nacm_module_write.sh
index 707fd20d..ecffa7f1 100755
--- a/test/test_nacm_module_write.sh
+++ b/test/test_nacm_module_write.sh
@@ -48,6 +48,7 @@ cat < $cfg
/usr/local/var/$APPNAME
false
internal
+ true
EOF
diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh
index a55b456b..bc072b75 100755
--- a/test/test_nacm_protocol.sh
+++ b/test/test_nacm_protocol.sh
@@ -52,6 +52,7 @@ cat < $cfg
false
internal
none
+ true
EOF
diff --git a/test/test_perf_mem.sh b/test/test_perf_mem.sh
index 941ee377..4c5f9ee4 100755
--- a/test/test_perf_mem.sh
+++ b/test/test_perf_mem.sh
@@ -27,9 +27,6 @@ module scaling{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
- import "clixon-config" {
- prefix cc;
- }
container x {
list y {
key "a";
diff --git a/test/test_restconf_patch.sh b/test/test_restconf_patch.sh
index 052c93f8..d4181b36 100755
--- a/test/test_restconf_patch.sh
+++ b/test/test_restconf_patch.sh
@@ -26,6 +26,7 @@ cat < $cfg
/usr/local/var/$APPNAME
$dir
internal
+ true
EOF
diff --git a/test/test_startup.sh b/test/test_startup.sh
index b27cd3fe..b197d683 100755
--- a/test/test_startup.sh
+++ b/test/test_startup.sh
@@ -40,7 +40,6 @@ cat < $cfg
init
$format
-
EOF
# Create running-db containin the interface "run" OK
diff --git a/test/test_upgrade_quit.sh b/test/test_upgrade_quit.sh
index dce92d99..e5575d6c 100755
--- a/test/test_upgrade_quit.sh
+++ b/test/test_upgrade_quit.sh
@@ -40,6 +40,11 @@ module interfaces{
reference
"RFC 2863: The Interfaces Group MIB";
}
+ leaf foo{
+ description "Should not appear";
+ type string;
+ default "bar";
+ }
container interfaces {
description
"Interface configuration parameters.";
@@ -52,8 +57,11 @@ module interfaces{
leaf description {
type string;
}
-
-
+ leaf foo{
+ description "Should not appear";
+ type string;
+ default "bar";
+ }
leaf type {
type string;
mandatory true;
@@ -124,6 +132,11 @@ module interfaces{
reference
"RFC 2863: The Interfaces Group MIB";
}
+ leaf foo{
+ description "Should not appear";
+ type string;
+ default "fie";
+ }
container interfaces {
description
"Interface configuration parameters.";
@@ -133,6 +146,11 @@ module interfaces{
leaf name {
type string;
}
+ leaf foo{
+ description "Should not appear";
+ type string;
+ default "bar";
+ }
container docs{
description "Original description is wrapped and renamed";
leaf descr {
@@ -290,7 +308,6 @@ XML='e0
ALL="$MODSTATE$XML"
# -u means trigger example upgrade
-new "test params: -s startup -f $cfg -- -u"
# kill old backend (if any)
new "kill old backend"
@@ -301,6 +318,7 @@ fi
new "start backend -s startup -f $cfg -q -- -u"
output=$(sudo $clixon_backend -F -D $DBG -s startup -f $cfg -q -- -u)
#echo "$output"
+
if [ "$ALL" != "$output" ]; then
err "$ALL" "$output"
fi
diff --git a/yang/clixon/clixon-config@2020-06-17.yang b/yang/clixon/clixon-config@2020-06-17.yang
index 70b2a00f..3ae88cd9 100644
--- a/yang/clixon/clixon-config@2020-06-17.yang
+++ b/yang/clixon/clixon-config@2020-06-17.yang
@@ -46,9 +46,8 @@ module clixon-config {
description
"Added: CLICON_CLI_LINES_DEFAULT
Added enum HIDE to CLICON_CLI_GENMODEL
- Added CLICON_SSL_SERVER_CERT
- Added CLICON_SSL_SERVER_KEY
- Added CLICON_SSL_CA_CERT";
+ Added CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
+ Added CLICON_NACM_DISABLED_ON_EMPTY";
}
revision 2020-04-23 {
description
@@ -719,6 +718,20 @@ module clixon-config {
exact for example, this user must exist and be used, otherwise
another user (such as root or www) can pose as it.";
}
+ leaf CLICON_NACM_DISABLED_ON_EMPTY {
+ type boolean;
+ default false;
+ description
+ "RFC 8341 and ietf-netconf-acm@2018-02-14.yang defines enable-nacm as true by
+ default. Since also write-default is deny by default it leads to that empty
+ configs can not be edited.
+ This means that a startup config must always have a NACM configuration or
+ that the NACM recovery session is used to edit an empty config.
+ If this option is set, Clixon disables NACM if a datastore is empty on load.
+ Note that it only makes the check on initial load, not if a store 'becomes'
+ empty, but enables a clixon nacm system to start empty and add an NACM
+ config after boot.";
+ }
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;