This commit is contained in:
Olof hagsand 2019-05-14 14:11:43 +02:00
commit 0b0fef12cc
22 changed files with 663 additions and 249 deletions

View file

@ -53,6 +53,15 @@
### API changes on existing features (you may need to change your code) ### API changes on existing features (you may need to change your code)
* Clixon transaction mechanism has changed which may affect your backend plugin callbacks:
* Validate-only transactions are terminated by an `end` or `abort` callback. Now all started transactions are terminated either by an `end` or `abort` without exceptions
* Validate-only transactions used to be terminated by `complete`
* If a commit user callback fails, a new `revert` callback will be made to plugins that have made a succesful commit.
* Clixon used to play the (already made) commit callbacks in reverse order
* Clixon config option `CLICON_XMLDB_CACHE` renamed to `CLICON_DATASTORE_CACHE` and changed type from `boolean` to `datastore_cache`
* Change code from: `clicon_option_bool(h, "CLICON_XMLDB_CACHE")` to `clicon_datastore_cache(h) == DATASTORE_CACHE`
* Type `datastore_cache` have values: nocache, cache, or cache-zerocopy
* `xmldb_get1` removed (functionality merged with `xmldb_get`)
* Non-key list now not accepted in edit-config (before only on validation) * Non-key list now not accepted in edit-config (before only on validation)
* Changed return values in internal functions * Changed return values in internal functions
* These functions are affected: `netconf_trymerge`, `startup_module_state`, `yang_modules_state_get` * These functions are affected: `netconf_trymerge`, `startup_module_state`, `yang_modules_state_get`

View file

@ -336,7 +336,10 @@ from_client_get_config(clicon_handle h,
if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/"; xpath="/";
if (xmldb_get(h, db, xpath, &xret, NULL) < 0){ /* Note xret can be pruned by nacm below (and change name),
* so zero-copy cant be used
*/
if (xmldb_get(h, db, xpath, 1, &xret, NULL) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0) if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done; goto done;
goto ok; goto ok;
@ -800,14 +803,16 @@ from_client_get(clicon_handle h,
if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/"; xpath="/";
/* Get config */ /* Get config
if (xmldb_get(h, "running", xpath, &xret, NULL) < 0){ * Note xret can be pruned by nacm below and change name and
* metrged with state data, so zero-copy cant be used
*/
if (xmldb_get(h, "running", xpath, 1, &xret, NULL) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0) if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done; goto done;
goto ok; goto ok;
} }
/* Get state data from plugins as defined by plugin_statedata(), if any */ /* Get state data from plugins as defined by plugin_statedata(), if any */
assert(xret);
clicon_err_reset(); clicon_err_reset();
if ((ret = client_statedata(h, xpath, &xret)) < 0) if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done; goto done;

View file

@ -182,7 +182,7 @@ startup_common(clicon_handle h,
if ((msd = modstate_diff_new()) == NULL) if ((msd = modstate_diff_new()) == NULL)
goto done; goto done;
clicon_debug(1, "Reading startup config from %s", db); clicon_debug(1, "Reading startup config from %s", db);
if (xmldb_get1(h, db, "/", &xt, msd) < 0) if (xmldb_get(h, db, "/", 0, &xt, msd) < 0)
goto done; goto done;
/* Clear flags xpath for get */ /* Clear flags xpath for get */
xml_apply0(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, xml_apply0(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
@ -271,25 +271,26 @@ startup_validate(clicon_handle h,
/* Handcraft a transition with only target and add trees */ /* Handcraft a transition with only target and add trees */
if ((td = transaction_new()) == NULL) if ((td = transaction_new()) == NULL)
goto done; goto done;
if ((ret = startup_common(h, db, td, cbret)) < 0) if ((ret = startup_common(h, db, td, cbret)) < 0){
plugin_transaction_abort(h, td);
goto done; goto done;
if (ret == 0) }
if (ret == 0){
plugin_transaction_abort(h, td);
goto fail; goto fail;
}
plugin_transaction_end(h, td);
/* Clear cached trees from default values and marking */ /* Clear cached trees from default values and marking */
if (xmldb_get1_clear(h, db) < 0) if (xmldb_get_clear(h, td->td_target) < 0)
goto done; goto done;
if (xtr){ if (xtr){
*xtr = td->td_target; *xtr = td->td_target;
td->td_target = NULL; td->td_target = NULL;
} }
retval = 1; retval = 1;
done: done:
if (td){ if (td){
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ xmldb_get_free(h, &td->td_target);
/* xmldb_get1 requires free only if not cache */
td->td_target = NULL;
td->td_src = NULL;
}
transaction_free(td); transaction_free(td);
} }
return retval; return retval;
@ -332,7 +333,7 @@ startup_commit(clicon_handle h,
if (plugin_transaction_commit(h, td) < 0) if (plugin_transaction_commit(h, td) < 0)
goto done; goto done;
/* Clear cached trees from default values and marking */ /* Clear cached trees from default values and marking */
if (xmldb_get1_clear(h, db) < 0) if (xmldb_get_clear(h, td->td_target) < 0)
goto done; goto done;
/* [Delete and] create running db */ /* [Delete and] create running db */
@ -352,15 +353,12 @@ startup_commit(clicon_handle h,
goto fail; goto fail;
/* 10. Call plugin transaction end callbacks */ /* 10. Call plugin transaction end callbacks */
plugin_transaction_end(h, td); plugin_transaction_end(h, td);
retval = 1; retval = 1;
done: done:
if (td){ if (td){
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (retval < 1)
/* xmldb_get1 requires free only if not cache */ plugin_transaction_abort(h, td);
td->td_target = NULL; xmldb_get_free(h, &td->td_target);
td->td_src = NULL;
}
transaction_free(td); transaction_free(td);
} }
return retval; return retval;
@ -398,7 +396,7 @@ from_validate_common(clicon_handle h,
goto done; goto done;
} }
/* This is the state we are going to */ /* This is the state we are going to */
if (xmldb_get1(h, candidate, "/", &td->td_target, NULL) < 0) if (xmldb_get(h, candidate, "/", 0, &td->td_target, NULL) < 0)
goto done; goto done;
/* Clear flags xpath for get */ /* Clear flags xpath for get */
@ -416,7 +414,7 @@ from_validate_common(clicon_handle h,
/* 2. Parse xml trees /* 2. Parse xml trees
* This is the state we are going from */ * This is the state we are going from */
if (xmldb_get1(h, "running", "/", &td->td_src, NULL) < 0) if (xmldb_get(h, "running", "/", 0, &td->td_src, NULL) < 0)
goto done; goto done;
/* Clear flags xpath for get */ /* Clear flags xpath for get */
xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
@ -521,9 +519,9 @@ candidate_commit(clicon_handle h,
goto done; goto done;
/* Clear cached trees from default values and marking */ /* Clear cached trees from default values and marking */
if (xmldb_get1_clear(h, candidate) < 0) if (xmldb_get_clear(h, td->td_target) < 0)
goto done; goto done;
if (xmldb_get1_clear(h, "running") < 0) if (xmldb_get_clear(h, td->td_src) < 0)
goto done; goto done;
/* Optionally write (potentially modified) tree back to candidate /* Optionally write (potentially modified) tree back to candidate
@ -556,14 +554,11 @@ candidate_commit(clicon_handle h,
retval = 1; retval = 1;
done: done:
/* In case of failure (or error), call plugin transaction termination callbacks */ /* In case of failure (or error), call plugin transaction termination callbacks */
if (retval < 1 && td)
plugin_transaction_abort(h, td);
if (td){ if (td){
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (retval < 1)
/* xmldb_get1 requires free only if not cache */ plugin_transaction_abort(h, td);
td->td_target = NULL; xmldb_get_free(h, &td->td_target);
td->td_src = NULL; xmldb_get_free(h, &td->td_src);
}
transaction_free(td); transaction_free(td);
} }
return retval; return retval;
@ -741,39 +736,40 @@ from_client_validate(clicon_handle h,
goto done; goto done;
/* Common steps (with commit) */ /* Common steps (with commit) */
if ((ret = from_validate_common(h, db, td, cbret)) < 1){ if ((ret = from_validate_common(h, db, td, cbret)) < 1){
clicon_debug(1, "Validate %s failed", db); plugin_transaction_abort(h, td);
if (ret < 0){ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done;
goto done;
}
goto ok; goto ok;
} }
/* Clear cached trees from default values and marking */ /* Clear cached trees from default values and marking */
if (xmldb_get1_clear(h, db) < 0) if (xmldb_get_clear(h, td->td_target) < 0)
goto done; goto done;
if (xmldb_get1_clear(h, "running") < 0) if (xmldb_get_clear(h, td->td_src) < 0 ||
xmldb_get_clear(h, td->td_target) < 0){
plugin_transaction_abort(h, td);
goto done; goto done;
}
/* Optionally write (potentially modified) tree back to candidate */ /* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
plugin_transaction_abort(h, td);
if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0) clicon_username_get(h), cbret)) < 0)
goto done; goto done;
goto ok; goto ok;
} }
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
/* Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
ok: ok:
retval = 0; retval = 0;
done: done:
if (retval < 0 && td)
plugin_transaction_abort(h, td);
if (td){ if (td){
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (retval < 0)
/* xmldb_get1 requires free only if not cache */ plugin_transaction_abort(h, td);
td->td_target = NULL; xmldb_get_free(h, &td->td_target);
td->td_src = NULL; xmldb_get_free(h, &td->td_src);
} transaction_free(td);
transaction_free(td);
} }
return retval; return retval;
} /* from_client_validate */ } /* from_client_validate */

View file

@ -295,28 +295,14 @@ plugin_transaction_revert(clicon_handle h,
int nr) int nr)
{ {
int retval = 0; int retval = 0;
transaction_data_t tr; /* revert transaction */
clixon_plugin *cp = NULL; clixon_plugin *cp = NULL;
trans_cb_t *fn; trans_cb_t *fn;
/* Create a new reversed transaction from the original where src and target
are swapped */
memcpy(&tr, td, sizeof(tr));
tr.td_src = td->td_target;
tr.td_target = td->td_src;
tr.td_dlen = td->td_alen;
tr.td_dvec = td->td_avec;
tr.td_alen = td->td_dlen;
tr.td_avec = td->td_dvec;
tr.td_clen = td->td_clen;
tr.td_scvec = td->td_tcvec;
tr.td_tcvec = td->td_scvec;
while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
if ((fn = cp->cp_api.ca_trans_commit) == NULL) if ((fn = cp->cp_api.ca_trans_revert) == NULL)
continue; continue;
if ((retval = fn(h, (transaction_data)&tr)) < 0){ if ((retval = fn(h, (transaction_data)td)) < 0){
clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed", clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_revert callback failed",
__FUNCTION__, cp->cp_name); __FUNCTION__, cp->cp_name);
break; break;
} }

View file

@ -102,13 +102,12 @@ db_merge(clicon_handle h,
cxobj *xt = NULL; cxobj *xt = NULL;
/* Get data as xml from db1 */ /* Get data as xml from db1 */
if (xmldb_get(h, (char*)db1, NULL, &xt, NULL) < 0) if (xmldb_get(h, (char*)db1, NULL, 0, &xt, NULL) < 0)
goto done; goto done;
/* Merge xml into db2. Without commit */ /* Merge xml into db2. Without commit */
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret); retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret);
done: done:
if (xt) xmldb_get_free(h, &xt);
xml_free(xt);
return retval; return retval;
} }
@ -270,8 +269,7 @@ startup_extraxml(clicon_handle h,
ok: ok:
retval = 1; retval = 1;
done: done:
if (xt && !clicon_option_bool(h, "CLICON_XMLDB_CACHE")) xmldb_get_free(h, &xt);
xml_free(xt);
if (xmldb_delete(h, db) != 0 && errno != ENOENT) if (xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1; return -1;
return retval; return retval;

View file

@ -15,7 +15,8 @@
* [How do I use restconf?](#how-do-i-use-restconf) * [How do I use restconf?](#how-do-i-use-restconf)
* [What about reference documentation?](#what-about-reference-documentation) * [What about reference documentation?](#what-about-reference-documentation)
* [How is configuration data stored?](#how-is-configuration-data-stored) * [How is configuration data stored?](#how-is-configuration-data-stored)
* [What is validate and commit?](#what-is-validate-and-commit) * [What is validate and commit?](#what-is-validate-and-commi)t
* [Does Clixon support transactions?](#does-clixon-support-transactions)
* [What is a Clixon configuration file?](#what-is-a-clixon-configuration-file) * [What is a Clixon configuration file?](#what-is-a-clixon-configuration-file)
* [How are Clixon configuration files found?](#how-are-clixon-configuration-files-found) * [How are Clixon configuration files found?](#how-are-clixon-configuration-files-found)
* [Can I modify clixon options at runtime?](#can-i-modify-clixon-options-at-runtime) * [Can I modify clixon options at runtime?](#can-i-modify-clixon-options-at-runtime)
@ -227,6 +228,42 @@ A clixon developer writes commit functions to incrementaly upgrade a
system state based on configuration changes. Writing commit callbacks system state based on configuration changes. Writing commit callbacks
is the core functionality of a clixon system. is the core functionality of a clixon system.
## Does Clixon support transactions?
Yes. The netconf validation and commit operation is implemented in
Clixon by a transaction mechanism, which ensures that user-written
plugin callbacks are invoked atomically and revert on error. If you
have two plugins, for example, a transaction sequence looks like the
following:
```
Backend Plugin1 Plugin2
| | |
+--------->+--------->+ begin
| | |
+--------->+--------->+ validate
| | |
+--------->+--------->+ commit
| | |
+--------->+--------->+ end
```
If an error occurs in the commit call of Plugin2, for example,
the transaction is aborted and the commit reverted:
```
Backend Plugin1 Plugin2
| | |
+--------->+--------->+ begin
| | |
+--------->+--------->+ validate
| | |
+--------->+---->X + commit error
| | |
+--------->+ + revert
| | |
+--------->+--------->+ abort
```
## What is a Clixon configuration file? ## What is a Clixon configuration file?
Clixon options are stored in an XML configuration file. The default Clixon options are stored in an XML configuration file. The default
@ -430,7 +467,6 @@ Each plugin is initiated with an API struct followed by a plugin init function a
``` ```
For more info see [../example/main/README.md] For more info see [../example/main/README.md]
## How do I write a commit function? ## How do I write a commit function?
In the example, you write a commit function in example_backend.c. In the example, you write a commit function in example_backend.c.
Every time a commit is made, transaction_commit() is called in the Every time a commit is made, transaction_commit() is called in the

View file

@ -14,7 +14,7 @@
## Content ## Content
This directory contains a Clixon example which includes a simple example. It contains the following files: This directory contains a Clixon example used primarily for testing. It can be used as a basis for making new Clixon applications. But please consider also the minimal [hello](../hello) example as well. It contains the following files:
* `example.xml` The configuration file. See [yang/clixon-config@<date>.yang](../../yang/clixon-config@2019-03-05.yang) for the documentation of all available fields. * `example.xml` The configuration file. See [yang/clixon-config@<date>.yang](../../yang/clixon-config@2019-03-05.yang) for the documentation of all available fields.
* `clixon-example@2019-01-13.yang` The yang spec of the example. * `clixon-example@2019-01-13.yang` The yang spec of the example.
* `example_cli.cli` CLIgen specification. * `example_cli.cli` CLIgen specification.

View file

@ -85,8 +85,7 @@ main_begin(clicon_handle h,
transaction_data td) transaction_data td)
{ {
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "begin"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0; return 0;
} }
/*! This is called on validate (and commit). Check validity of candidate /*! This is called on validate (and commit). Check validity of candidate
@ -96,7 +95,7 @@ main_validate(clicon_handle h,
transaction_data td) transaction_data td)
{ {
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "validate"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0; return 0;
} }
@ -105,7 +104,7 @@ main_complete(clicon_handle h,
transaction_data td) transaction_data td)
{ {
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "complete"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0; return 0;
} }
@ -113,7 +112,7 @@ main_complete(clicon_handle h,
*/ */
int int
main_commit(clicon_handle h, main_commit(clicon_handle h,
transaction_data td) transaction_data td)
{ {
cxobj *target = transaction_target(td); /* wanted XML tree */ cxobj *target = transaction_target(td); /* wanted XML tree */
cxobj **vec = NULL; cxobj **vec = NULL;
@ -121,7 +120,8 @@ main_commit(clicon_handle h,
size_t len; size_t len;
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "commit"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
/* Get all added i/fs */ /* Get all added i/fs */
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0) if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1; return -1;
@ -134,12 +134,21 @@ main_commit(clicon_handle h,
return 0; return 0;
} }
int
main_revert(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
int int
main_end(clicon_handle h, main_end(clicon_handle h,
transaction_data td) transaction_data td)
{ {
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "end"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0; return 0;
} }
@ -148,7 +157,7 @@ main_abort(clicon_handle h,
transaction_data td) transaction_data td)
{ {
if (_transaction_log) if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, "abort"); transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0; return 0;
} }
@ -568,6 +577,7 @@ static clixon_plugin_api api = {
.ca_trans_validate=main_validate, /* trans validate */ .ca_trans_validate=main_validate, /* trans validate */
.ca_trans_complete=main_complete, /* trans complete */ .ca_trans_complete=main_complete, /* trans complete */
.ca_trans_commit=main_commit, /* trans commit */ .ca_trans_commit=main_commit, /* trans commit */
.ca_trans_revert=main_revert, /* trans revert */
.ca_trans_end=main_end, /* trans end */ .ca_trans_end=main_end, /* trans end */
.ca_trans_abort=main_abort /* trans abort */ .ca_trans_abort=main_abort /* trans abort */
}; };
@ -594,7 +604,7 @@ clixon_plugin_init(clicon_handle h)
goto done; goto done;
opterr = 0; opterr = 0;
optind = 1; optind = 1;
while ((c = getopt(argc, argv, "rsut")) != -1) while ((c = getopt(argc, argv, "rsut:")) != -1)
switch (c) { switch (c) {
case 'r': case 'r':
_reset = 1; _reset = 1;

View file

@ -32,11 +32,16 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* *
* IETF yang routing example * Secondary backend for testing more than one backend plugin, with the following
* Secondary backend for testing more than one backend plugin * features:
* - nacm
* - transaction test
* The transaction test is test/test_transaction.sh where a user-error is triggered
* by this plugin if started with -- -t <xpath>. _transaction_xpath is then set
* and triggers a validation error if it matches. The error also toggles between
* validation and commit errors.
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -55,6 +60,97 @@
/* These include signatures for plugin and transaction callbacks. */ /* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h> #include <clixon/clixon_backend.h>
/*! Variable to control transaction logging (for debug)
* If set, call syslog for every transaction callback
*/
static int _transaction_log = 0;
/*! Variable for failing validate and commit, if set, fail on validate vs commit
*/
static char * _transaction_xpath = NULL;
static int _transaction_error_toggle = 0; /* fail at validate vs commit */
int
nacm_begin(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
/*! This is called on validate (and commit). Check validity of candidate
*/
int
nacm_validate(clicon_handle h,
transaction_data td)
{
if (_transaction_log){
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
if (_transaction_error_toggle==0 &&
xpath_first(transaction_target(td), "%s", _transaction_xpath)){
_transaction_error_toggle=1; /* toggle if triggered */
clicon_err(OE_XML, 0, "User error");
return -1; /* induce fail */
}
}
return 0;
}
int
nacm_complete(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
/*! This is called on commit. Identify modifications and adjust machine state
*/
int
nacm_commit(clicon_handle h,
transaction_data td)
{
cxobj *target = transaction_target(td); /* wanted XML tree */
if (_transaction_log){
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
if (_transaction_error_toggle==1 &&
xpath_first(target, "%s", _transaction_xpath)){
_transaction_error_toggle=0; /* toggle if triggered */
clicon_err(OE_XML, 0, "User error");
return -1; /* induce fail */
}
}
return 0;
}
int
nacm_revert(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
int
nacm_end(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
int
nacm_abort(clicon_handle h,
transaction_data td)
{
if (_transaction_log)
transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
return 0;
}
/*! Called to get NACM state data /*! Called to get NACM state data
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -88,21 +184,21 @@ nacm_statedata(clicon_handle h,
return retval; return retval;
} }
int
plugin_start(clicon_handle h)
{
return 0;
}
clixon_plugin_api *clixon_plugin_init(clicon_handle h); clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = { static clixon_plugin_api api = {
"nacm", /* name */ /*--- Common fields. ---*/ "nacm", /* name */ /*--- Common fields. ---*/
clixon_plugin_init, /* init */ clixon_plugin_init, /* init */
plugin_start, /* start */ NULL, /* start */
NULL, /* exit */ NULL, /* exit */
.ca_reset=NULL, /* reset */
.ca_statedata=nacm_statedata, /* statedata */ .ca_statedata=nacm_statedata, /* statedata */
.ca_trans_begin=nacm_begin, /* trans begin */
.ca_trans_validate=nacm_validate, /* trans validate */
.ca_trans_complete=nacm_complete, /* trans complete */
.ca_trans_commit=nacm_commit, /* trans commit */
.ca_trans_revert=nacm_revert, /* trans revert */
.ca_trans_end=nacm_end, /* trans end */
.ca_trans_abort=nacm_abort /* trans abort */
}; };
/*! Backend plugin initialization /*! Backend plugin initialization
@ -113,13 +209,34 @@ static clixon_plugin_api api = {
clixon_plugin_api * clixon_plugin_api *
clixon_plugin_init(clicon_handle h) clixon_plugin_init(clicon_handle h)
{ {
char *nacm_mode; char *nacm_mode;
int argc; /* command-line options (after --) */
char **argv;
int c;
clicon_debug(1, "%s backend nacm", __FUNCTION__); clicon_debug(1, "%s backend nacm", __FUNCTION__);
/* Get user command-line options (after --) */
if (clicon_argv_get(h, &argc, &argv) < 0)
goto done;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "rsut:")) != -1)
switch (c) {
case 't': /* transaction log */
_transaction_log = 1;
_transaction_xpath = optarg;
break;
}
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){ if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
clicon_log(LOG_DEBUG, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__); clicon_log(LOG_DEBUG, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
return NULL; /* Skip nacm module if not enabled _unless_ we use transaction tests */
if (_transaction_log == 0)
return NULL;
} }
/* Return plugin API */
return &api; return &api;
done:
return NULL;
} }

View file

@ -48,9 +48,9 @@ int xmldb_db2file(clicon_handle h, const char *db, char **filename);
int xmldb_validate_db(const char *db); int xmldb_validate_db(const char *db);
int xmldb_connect(clicon_handle h); int xmldb_connect(clicon_handle h);
int xmldb_disconnect(clicon_handle h); int xmldb_disconnect(clicon_handle h);
int xmldb_get(clicon_handle h, const char *db, char *xpath, cxobj **xtop, modstate_diff_t *msd); /* in clixon_datastore_read.[ch] */ int xmldb_get(clicon_handle h, const char *db, char *xpath, int copy, cxobj **xtop, modstate_diff_t *msd); /* in clixon_datastore_read.[ch] */
int xmldb_get1(clicon_handle h, const char *db, char *xpath, cxobj **xtop, modstate_diff_t *msd); /* in clixon_datastore_read.[ch] */ int xmldb_get_clear(clicon_handle h, cxobj *x);
int xmldb_get1_clear(clicon_handle h, const char *db); int xmldb_get_free(clicon_handle h, cxobj **xp);
int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret); /* in clixon_datastore_write.[ch] */ int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret); /* in clixon_datastore_write.[ch] */
int xmldb_copy(clicon_handle h, const char *from, const char *to); int xmldb_copy(clicon_handle h, const char *from, const char *to);
int xmldb_lock(clicon_handle h, const char *db, int pid); int xmldb_lock(clicon_handle h, const char *db, int pid);

View file

@ -73,6 +73,15 @@ enum startup_mode_t{
SM_INIT SM_INIT
}; };
/*! Datastore cache behaviour, see clixon_datastore.[ch]
* See config option type datastore_cache in clixon-config.yang
*/
enum datastore_cache{
DATASTORE_NOCACHE,
DATASTORE_CACHE,
DATASTORE_CACHE_ZEROCOPY
};
/* /*
* Prototypes * Prototypes
*/ */
@ -163,6 +172,7 @@ int clicon_sock_family(clicon_handle h);
int clicon_sock_port(clicon_handle h); int clicon_sock_port(clicon_handle h);
int clicon_autocommit(clicon_handle h); int clicon_autocommit(clicon_handle h);
int clicon_startup_mode(clicon_handle h); int clicon_startup_mode(clicon_handle h);
enum datastore_cache clicon_datastore_cache(clicon_handle h);
/*-- Specific option access functions for non-yang options --*/ /*-- Specific option access functions for non-yang options --*/
int clicon_quiet_mode(clicon_handle h); int clicon_quiet_mode(clicon_handle h);

View file

@ -177,6 +177,7 @@ struct clixon_plugin_api{
trans_cb_t *cb_trans_validate; /* Transaction validation */ trans_cb_t *cb_trans_validate; /* Transaction validation */
trans_cb_t *cb_trans_complete; /* Transaction validation complete */ trans_cb_t *cb_trans_complete; /* Transaction validation complete */
trans_cb_t *cb_trans_commit; /* Transaction commit */ trans_cb_t *cb_trans_commit; /* Transaction commit */
trans_cb_t *cb_trans_revert; /* Transaction revert */
trans_cb_t *cb_trans_end; /* Transaction completed */ trans_cb_t *cb_trans_end; /* Transaction completed */
trans_cb_t *cb_trans_abort; /* Transaction aborted */ trans_cb_t *cb_trans_abort; /* Transaction aborted */
@ -195,6 +196,7 @@ struct clixon_plugin_api{
#define ca_trans_validate u.cau_backend.cb_trans_validate #define ca_trans_validate u.cau_backend.cb_trans_validate
#define ca_trans_complete u.cau_backend.cb_trans_complete #define ca_trans_complete u.cau_backend.cb_trans_complete
#define ca_trans_commit u.cau_backend.cb_trans_commit #define ca_trans_commit u.cau_backend.cb_trans_commit
#define ca_trans_revert u.cau_backend.cb_trans_revert
#define ca_trans_end u.cau_backend.cb_trans_end #define ca_trans_end u.cau_backend.cb_trans_end
#define ca_trans_abort u.cau_backend.cb_trans_abort #define ca_trans_abort u.cau_backend.cb_trans_abort

View file

@ -176,7 +176,6 @@ xmldb_disconnect(clicon_handle h)
return retval; return retval;
} }
/*! Copy database from db1 to db2 /*! Copy database from db1 to db2
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] from Source database * @param[in] from Source database
@ -199,7 +198,7 @@ xmldb_copy(clicon_handle h,
cxobj *x2 = NULL; /* to */ cxobj *x2 = NULL; /* to */
/* XXX lock */ /* XXX lock */
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
/* Copy in-memory cache */ /* Copy in-memory cache */
/* 1. "to" xml tree in x1 */ /* 1. "to" xml tree in x1 */
if ((de1 = clicon_db_elmnt_get(h, from)) != NULL) if ((de1 = clicon_db_elmnt_get(h, from)) != NULL)
@ -387,7 +386,7 @@ xmldb_delete(clicon_handle h,
cxobj *xt = NULL; cxobj *xt = NULL;
struct stat sb; struct stat sb;
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
if ((de = clicon_db_elmnt_get(h, db)) != NULL){ if ((de = clicon_db_elmnt_get(h, db)) != NULL){
if ((xt = de->de_xml) != NULL){ if ((xt = de->de_xml) != NULL){
xml_free(xt); xml_free(xt);
@ -425,7 +424,7 @@ xmldb_create(clicon_handle h,
db_elmnt *de = NULL; db_elmnt *de = NULL;
cxobj *xt = NULL; cxobj *xt = NULL;
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ /* XXX This should not really happen? */ if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
if ((de = clicon_db_elmnt_get(h, db)) != NULL){ if ((de = clicon_db_elmnt_get(h, db)) != NULL){
if ((xt = de->de_xml) != NULL){ if ((xt = de->de_xml) != NULL){
xml_free(xt); xml_free(xt);

View file

@ -572,11 +572,11 @@ xmldb_get_cache(clicon_handle h,
* @retval -1 Error * @retval -1 Error
*/ */
static int static int
xmldb_get1_cache(clicon_handle h, xmldb_get_zerocopy(clicon_handle h,
const char *db, const char *db,
char *xpath, char *xpath,
cxobj **xtop, cxobj **xtop,
modstate_diff_t *msd) modstate_diff_t *msd)
{ {
int retval = -1; int retval = -1;
yang_stmt *yspec; yang_stmt *yspec;
@ -641,16 +641,29 @@ xmldb_get1_cache(clicon_handle h,
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] db Name of database to search in (filename including dir path * @param[in] db Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all * @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] xret Single return XML tree. Free with xml_free()
* @param[out] msd If set, return modules-state differences * @param[out] msd If set, return modules-state differences
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* There are two variants of the call:
* @code * @code
* cxobj *xt; * cxobj *xt;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", &xt, NULL) < 0) * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 0, &xt, NULL) < 0)
* err;
* # Code accessing and setting XML flags on xt, sorting, populate w yang spec,...
* xmldb_get_clear(h, xt); # Clear tree from default values and flags
* # (only if cache+zerocopy
* xmldb_get_free(h, &xt); # Free tree (only if not zero-copy)
* @endcode
* The second variant only applies to zerocopy cases where you want to force a copy
* since it may be too difficult to handle marked subtrees.
* @code
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt, NULL) < 0)
* err; * err;
* xml_free(xt); * xml_free(xt);
* @endcode * @endcode
*
* @note if xvec is given, then purge tree, if not return whole tree. * @note if xvec is given, then purge tree, if not return whole tree.
* @see xpath_vec * @see xpath_vec
*/ */
@ -658,84 +671,83 @@ int
xmldb_get(clicon_handle h, xmldb_get(clicon_handle h,
const char *db, const char *db,
char *xpath, char *xpath,
int copy,
cxobj **xret, cxobj **xret,
modstate_diff_t *msd) modstate_diff_t *msd)
{ {
int retval = -1; int retval = -1;
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")) switch (clicon_datastore_cache(h)){
case DATASTORE_NOCACHE:
/* Read from file into created/copy tree, prune non-matching xpath
* Add default values in copy
* Copy deleted by xmldb_free
*/
retval = xmldb_get_nocache(h, db, xpath, xret, msd);
break;
case DATASTORE_CACHE_ZEROCOPY:
/* Get cache (file if empty) mark xpath match in original tree
* add default values in original tree and return that.
* Default values and markings removed in xmldb_clear
*/
if (!copy){
retval = xmldb_get_zerocopy(h, db, xpath, xret, msd);
break;
}
/* fall through */
case DATASTORE_CACHE:
/* Get cache (file if empty) mark xpath match and copy marked into copy
* Add default values in copy, return copy
* Copy deleted by xmldb_free
*/
retval = xmldb_get_cache(h, db, xpath, xret, msd); retval = xmldb_get_cache(h, db, xpath, xret, msd);
else break;
retval = xmldb_get_nocache(h, db, xpath, xret, msd); }
return retval; return retval;
} }
/*! Get content of database using xpath. return a set of matching sub-trees /*! Clear cached xml tree obtained with xmldb_get, if zerocopy
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
* @param[in] h Clicon handle
* @param[in] db Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xret Single return XML tree. see note
* @param[out] msd If set, return modules-state differences
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", &xt, NULL) < 0)
* err;
* xml_free(xt);
* @endcode
* @note if xvec is given, then purge tree, if not return whole tree.
* @see xmldb_get This version uses direct cache access and needs to be
* cleanued up after use
* @see xmldb_get1_clear Must call after use
* @note If !CLICON_XMLDB_CACHE you need to free xret after use
* @note If CLICON_XMLDB_CACHE mark|change flags set, need to clear after call
*/
int
xmldb_get1(clicon_handle h,
const char *db,
char *xpath,
cxobj **xret,
modstate_diff_t *msd)
{
int retval = -1;
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE"))
retval = xmldb_get1_cache(h, db, xpath, xret, msd);
else
retval = xmldb_get_nocache(h, db, xpath, xret, msd);
return retval;
}
/*! Clear cached tree after accessed by xmldb_get1
* *
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] dbname Name of database to search in (filename including dir path * @param[in] db Name of datastore
* @see xmldb_get1 * "Clear" an xml tree means removing default values and resetting all flags.
* @see xmldb_get
*/ */
int int
xmldb_get1_clear(clicon_handle h, xmldb_get_clear(clicon_handle h,
const char *db) cxobj *x)
{ {
int retval = -1; int retval = -1;
db_elmnt *de = NULL;
if (!clicon_option_bool(h, "CLICON_XMLDB_CACHE")) if (clicon_datastore_cache(h) != DATASTORE_CACHE_ZEROCOPY)
goto ok; /* dont bother, tree is a copy */ goto ok;
de = clicon_db_elmnt_get(h, db); if (x == NULL)
if (de != NULL && de->de_xml != NULL){ goto ok;
/* clear XML tree of defaults */ /* clear XML tree of defaults */
if (xml_tree_prune_flagged(de->de_xml, XML_FLAG_DEFAULT, 1) < 0) if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
goto done; goto done;
/* clear mark and change */ /* clear mark and change */
xml_apply0(de->de_xml, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(0xff)); (void*)(0xff));
}
ok: ok:
retval = 0; retval = 0;
done: done:
return retval; return retval;
}
/*! Free xml tree obtained with xmldb_get, unless zerocopy
* @param[in] h Clixon handle
* @param[in,out] xp Pointer to XML cache.
* @see xmldb_get
*/
int
xmldb_get_free(clicon_handle h,
cxobj **xp)
{
if (*xp == NULL ||
clicon_datastore_cache(h) == DATASTORE_CACHE_ZEROCOPY)
return 0;
xml_free(*xp);
*xp = NULL;
return 0;
} }

View file

@ -206,6 +206,7 @@ buf2xml(FILE *f,
int i; int i;
uint32_t ind; uint32_t ind;
cxobj *xc; cxobj *xc;
size_t ret;
/* Read hdr /* Read hdr
* +-----+-----+-----+-----+-----+-----+-----+-----+ * +-----+-----+-----+-----+-----+-----+-----+-----+
@ -216,7 +217,7 @@ buf2xml(FILE *f,
clicon_err(OE_UNIX, errno, "fseek"); clicon_err(OE_UNIX, errno, "fseek");
goto done; goto done;
} }
if (fread(hdr, sizeof(char), sizeof(hdr), f) < 0){ if ((ret = fread(hdr, sizeof(char), sizeof(hdr), f)) != sizeof(hdr)){
clicon_err(OE_XML, errno, "fread"); clicon_err(OE_XML, errno, "fread");
goto done; goto done;
} }
@ -250,7 +251,7 @@ buf2xml(FILE *f,
clicon_err(OE_UNIX, errno, "malloc"); clicon_err(OE_UNIX, errno, "malloc");
goto done; goto done;
} }
if (fread(buf, sizeof(char), len-sizeof(hdr), f) < 0){ if ((ret = fread(buf, sizeof(char), len-sizeof(hdr), f)) != len-sizeof(hdr)){
clicon_err(OE_XML, errno, "fread"); clicon_err(OE_XML, errno, "fread");
goto done; goto done;
} }
@ -318,7 +319,7 @@ xml2buf(FILE *f,
int len1; int len1;
int ptr; int ptr;
int i; int i;
char *buf0; char *buf0 = NULL;
char *buf; char *buf;
uint32_t myindex; uint32_t myindex;

View file

@ -652,8 +652,9 @@ xmldb_put(clicon_handle h,
xml_name(x1)); xml_name(x1));
goto done; goto done;
} }
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){
if ((de = clicon_db_elmnt_get(h, db)) != NULL) if ((de = clicon_db_elmnt_get(h, db)) != NULL){
if (clicon_datastore_cache(h) != DATASTORE_NOCACHE)
x0 = de->de_xml; x0 = de->de_xml;
} }
/* If there is no xml x0 tree (in cache), then read it from file */ /* If there is no xml x0 tree (in cache), then read it from file */
@ -713,8 +714,9 @@ xmldb_put(clicon_handle h,
if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif #endif
/* Write back to datastore cache if first time */ /* Write back to datastore cache if first time */
if (clicon_option_bool(h, "CLICON_XMLDB_CACHE")){ if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
db_elmnt de0 = {0,}; db_elmnt de0 = {0,};
if (de != NULL) if (de != NULL)
de0 = *de; de0 = *de;
@ -770,7 +772,7 @@ xmldb_put(clicon_handle h,
free(dbfile); free(dbfile);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (!clicon_option_bool(h, "CLICON_XMLDB_CACHE") && x0) if (x0 && clicon_datastore_cache(h) == DATASTORE_NOCACHE)
xml_free(x0); xml_free(x0);
return retval; return retval;
fail: fail:

View file

@ -890,7 +890,7 @@ nacm_access_pre(clicon_handle h,
goto done; goto done;
} }
else if (strcmp(mode, "internal")==0){ else if (strcmp(mode, "internal")==0){
if (xmldb_get(h, "running", "nacm", &xnacm0, NULL) < 0) if (xmldb_get(h, "running", "nacm", 1, &xnacm0, NULL) < 0)
goto done; goto done;
} }
} }

View file

@ -543,30 +543,25 @@ clicon_cli_genmodel_completion(clicon_handle h)
return 0; return 0;
} }
static const map_str2int cli_genmodel_map[] = {
{"NONE", GT_NONE},
{"VARS", GT_VARS},
{"ALL", GT_ALL},
{NULL, -1}
};
/*! How to generate and show CLI syntax: VARS|ALL /*! How to generate and show CLI syntax: VARS|ALL
* @see clixon-config@<date>.yang CLICON_CLI_GENMODEL_TYPE * @see clixon-config@<date>.yang CLICON_CLI_GENMODEL_TYPE
*/ */
enum genmodel_type enum genmodel_type
clicon_cli_genmodel_type(clicon_handle h) clicon_cli_genmodel_type(clicon_handle h)
{ {
char *s; char *str;
enum genmodel_type gt = GT_ERR;
if (!clicon_option_exists(h, "CLICON_CLI_GENMODEL_TYPE")){ if ((str = clicon_option_str(h, "CLICON_CLI_GENMODEL_TYPE")) == NULL)
gt = GT_VARS; return GT_VARS;
goto done;
}
s = clicon_option_str(h, "CLICON_CLI_GENMODEL_TYPE");
if (strcmp(s, "NONE")==0)
gt = GT_NONE;
else else
if (strcmp(s, "VARS")==0) return clicon_str2int(cli_genmodel_map, str);
gt = GT_VARS;
else
if (strcmp(s, "ALL")==0)
gt = GT_ALL;
done:
return gt;
} }
/*! Get Dont include keys in cvec in cli vars callbacks /*! Get Dont include keys in cvec in cli vars callbacks
@ -638,6 +633,28 @@ clicon_startup_mode(clicon_handle h)
return clicon_str2int(startup_mode_map, mode); return clicon_str2int(startup_mode_map, mode);
} }
static const map_str2int datastore_cache_map[] = {
{"nocache", DATASTORE_NOCACHE},
{"cache", DATASTORE_CACHE},
{"cache-zerocopy", DATASTORE_CACHE_ZEROCOPY},
{NULL, -1}
};
/*! How to generate and show CLI syntax: VARS|ALL
* @see clixon-config@<date>.yang CLICON_CLI_GENMODEL_TYPE
*/
enum datastore_cache
clicon_datastore_cache(clicon_handle h)
{
char *str;
if ((str = clicon_option_str(h, "CLICON_DATASTORE_CACHE")) == NULL)
return DATASTORE_CACHE;
else
return clicon_str2int(datastore_cache_map, str);
}
/*--------------------------------------------------------------------- /*---------------------------------------------------------------------
* Specific option access functions for non-yang options * Specific option access functions for non-yang options
* Typically dynamic values and more complex datatypes, * Typically dynamic values and more complex datatypes,

View file

@ -36,7 +36,7 @@ cat <<EOF > $histfile
first line first line
EOF EOF
# NOTE Backend is not really use here # NOTE Backend is not really used here
new "test params: -f $cfg" new "test params: -f $cfg"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "kill old backend" new "kill old backend"
@ -59,22 +59,22 @@ if [ ! -f $histfile ]; then
err "$histfile" "not found" err "$histfile" "not found"
fi fi
new "Check it has two entries" new "Check histfile has two entries"
lines=$(cat $histfile | wc -l) nr=$(cat $histfile | wc -l)
if [ $lines -ne 2 ]; then if [ $nr -ne 2 ]; then
err "Line:$lines" "2" err "2" "$nr"
fi fi
new "check it contains first line" new "Check histfile contains first line"
nr=$(grep -c "example 42" $histfile) nr=$(grep -c "example 42" $histfile)
if [ $nr -ne 1 ]; then if [ $nr -ne 1 ]; then
err "Contains: example 42" "1" err "Contains: example 42" "$nr"
fi fi
new "Check it contains example 42" new "Check histfile contains example 42"
nr=$(grep -c "example 42" $histfile) nr=$(grep -c "example 42" $histfile)
if [ $nr -ne 1 ]; then if [ $nr -ne 1 ]; then
err "Contains: example 42" "1" err "1" "$nr"
fi fi
new "cli add entry and create newhist file" new "cli add entry and create newhist file"
@ -88,7 +88,28 @@ fi
new "check it contains example 43" new "check it contains example 43"
nr=$(grep -c "example 43" $dir/newhist) nr=$(grep -c "example 43" $dir/newhist)
if [ $nr -ne 1 ]; then if [ $nr -ne 1 ]; then
err "Contains: example 43" "1" err "1" "$nr"
fi
# Add a long (128 chars) string and see it survives
str128="1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"
new "cli add long line"
expecteof "$clixon_cli -f $cfg" 0 "$str128" "" 2> /dev/null # ignore error output
new "Check histfile contains long string"
nr=$(grep -c "$str128" $histfile)
if [ $nr -ne 1 ]; then
err "1" "$nr"
fi
new "cli load arrow-up save -> create two copies of long string"
expecteof "$clixon_cli -f $cfg" 0 "q" ""
expecteof "$clixon_cli -f $cfg" 0 "" "" 2> /dev/null
new "Check histfile contains two copies of long string"
nr=$(grep -c "$str128" $histfile)
if [ $nr -ne 2 ]; then
err "2" "$nr"
fi fi
if [ $BE -eq 0 ]; then if [ $BE -eq 0 ]; then

View file

@ -1,10 +1,20 @@
#!/bin/bash #!/bin/bash
# Transaction functionality # Transaction functionality
# The test uses a backend that logs to a file and a netconf client to push # The test uses two backend plugins (main and nacm) that logs to a file and a
# changes. The main example backend plugin logs to the file, and the test # netconf client to push operation. The tests then look at the log.
# verifies the logs. # The tests are as follows (first five only callbacks per se; then data vector tests)
# The yang is a list with three members, so that you can do add/delete/change # 1. Validate-only transaction
# in a single go. # 2. Commit transaction
# 3. Validate system-error (invalid type detected by system)
# 4. Validate user-error (invalidation by user callback)
# 5. Commit user-error (invalidation by user callback)
# -- to here only basic callback tests (that they occur). Below transaction data
# 6. Detailed transaction vector add/del/change tests
# For the last test, the yang is a list with three members, so that you can do
# add/delete/change in a single go.
# The user-error uses a trick feature in the example nacm plugin which is started
# with an "error-trigger" xpath which triggers an error. This also toggles between
# validation and commit errors
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -19,6 +29,9 @@ fyang=$dir/trans.yang
flog=$dir/backend.log flog=$dir/backend.log
touch $flog touch $flog
# Used as a trigger for user-validittion errors, eg <a>$errnr</a> is invalid
errnr=42
cat <<EOF > $fyang cat <<EOF > $fyang
module trans{ module trans{
yang-version 1.1; yang-version 1.1;
@ -31,8 +44,10 @@ module trans{
type int32; type int32;
} }
leaf b { leaf b {
description "change this"; description "change this (also use to check invalid)";
type int32; type int32{
range "0..100";
}
} }
leaf c { leaf c {
description "del this"; description "del this";
@ -54,7 +69,7 @@ cat <<EOF > $cfg
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE> <CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR> <CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR> <CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP> <!--CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP-->
<CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_DIR> <CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_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_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
@ -70,8 +85,9 @@ EOF
# Check statements in log # Check statements in log
checklog(){ checklog(){
s=$1 # statement s=$1 # statement
l0=$2 # linenr
new "Check $s in log" new "Check $s in log"
t=$(grep "$s" $flog) t=$(grep -n "transaction_log $s" $flog)
if [ -z "$t" ]; then if [ -z "$t" ]; then
echo -e "\e[31m\nError in Test$testnr [$testname]:" echo -e "\e[31m\nError in Test$testnr [$testname]:"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
@ -81,9 +97,19 @@ checklog(){
echo -e "\e[0m" echo -e "\e[0m"
exit -1 exit -1
fi fi
l1=$(echo "$t" | awk -F ":" '{print $1}')
if [ $l1 -ne $l0 ]; then
echo -e "\e[31m\nError in Test$testnr [$testname]:"
if [ $# -gt 0 ]; then
echo "Expected match on line $l0, found on $l1"
echo
fi
echo -e "\e[0m"
exit -1
fi
} }
new "test params: -f $cfg -l f$flog -- -t" new "test params: -f $cfg -l f$flog -- -t /x/y[a=$errnr]" # Fail on this
# Bring your own backend # Bring your own backend
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
# kill old backend (if any) # kill old backend (if any)
@ -92,47 +118,190 @@ if [ $BE -ne 0 ]; then
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend -s init -f $cfg -l f$flog -- -t" new "start backend -s init -f $cfg -l f$flog -- -t /x/y[a=$errnr]"
start_backend -s init -f $cfg -l f$flog -- -t # -t means transaction logging start_backend -s init -f $cfg -l f$flog -- -t /x/y[a=$errnr] # -t means transaction logging
new "waiting" new "waiting"
sleep $RCWAIT sleep $RCWAIT
fi fi
new "netconf base config (0) a,b,c" let nr=1
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 "Basic transaction to add top-level x"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$nr</a></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Commit base"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
let line=12 # Skipping basic transaction
# 1. validate(-only) transaction
let nr++
let line
new "1. Validate-only transaction"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$nr</a></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Validate-only validate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
xml="<y><a>$nr</a></y>"
for op in begin validate complete end; do
checklog "$nr main_$op add: $xml" $line
let line++
checklog "$nr nacm_$op add: $xml" $line
let line++
done
new "Validate-only discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# 2. Commit transaction
let nr++
new "2. Commit transaction config"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$nr</a></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Commit transaction: commit"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
xml="<y><a>$nr</a></y>"
for op in begin validate complete commit end; do
checklog "$nr main_$op add: $xml" $line
let line++
checklog "$nr nacm_$op add: $xml" $line
let line++
done
# 3. Validate only system-error (invalid type detected by system)
let nr++
new "3. Validate system-error config (9999 not in range)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$nr</a><b>9999</b></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Validate system-error validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>bad-element</error-tag><error-info><bad-element>b</bad-element></error-info><error-severity>error</error-severity><error-message>Number out of range: 9999</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Validate system-error discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
for op in begin abort; do
checklog "$nr main_$op add: <y><a>$nr</a><b>9999</b></y>" $line
let line++
checklog "$nr nacm_$op add: <y><a>$nr</a><b>9999</b></y>" $line
let line++
done
# 4. Validate only user-error (invalidation by user callback)
let nr++
new "4. Validate user-error config ($errnr is invalid)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$errnr</a></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Validate user-error validate (should fail)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>User error</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Validate user-error discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
for op in begin validate; do
checklog "$nr main_$op add: <y><a>$errnr</a></y>" $line
let line++
checklog "$nr nacm_$op add: <y><a>$errnr</a></y>" $line
let line++
done
let line++ # error message
for op in abort; do
checklog "$nr main_$op add: <y><a>$errnr</a></y>" $line
let line++
checklog "$nr nacm_$op add: <y><a>$errnr</a></y>" $line
let line++
done
# 5. Commit user-error (invalidation by user callback)
# XXX Note Validate-only user-error must immediately preceede this due to toggling
# in nacm/transaction example test module
let nr++
new "5. Commit user-error ($errnr is invalid)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$errnr</a></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Commit user-error commit"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>User error</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Commit user-error discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
for op in begin validate complete commit; do
checklog "$nr main_$op add: <y><a>$errnr</a></y>" $line
let line++
checklog "$nr nacm_$op add: <y><a>$errnr</a></y>" $line
let line++
done
let line++ # error message
checklog "$nr main_revert add: <y><a>$errnr</a></y>" $line
let line++
for op in abort; do
checklog "$nr main_$op add: <y><a>$errnr</a></y>" $line
let line++
checklog "$nr nacm_$op add: <y><a>$errnr</a></y>" $line
let line++
done
# 6. Detailed transaction vector add/del/change tests
let nr++
let base=nr
new "Add base <a>$base entry"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$base</a><b>0</b><c>0</c></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf commit base" new "netconf commit base"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
#Ignore #Ignore
let line+=10
new "netconf mixed change: change b, del c, add d" let nr++
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 "6. 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>$base</a><b>42</b><d>0</d></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf commit change" new "netconf commit change"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
# Check complete transaction 2: # Check complete transaction $nr:
for op in begin validate complete commit; do for op in begin validate complete commit; do
checklog "transaction_log 2 $op add: <d>0</d>" checklog "$nr main_$op add: <d>0</d>" $line
checklog "transaction_log 2 $op change: <b>0</b><b>42</b>" let line++
checklog "$nr main_$op change: <b>0</b><b>42</b>" $line
let line++
checklog "$nr nacm_$op add: <d>0</d>" $line
let line++
checklog "$nr nacm_$op change: <b>0</b><b>42</b>" $line
let line++
done 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 " # End is special because change does not haveold element
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>]]>]]>$' checklog "$nr main_end add: <d>0</d>" $line
let line++
checklog "$nr main_end change: <b>42</b>" $line
let line+=3 # skip nacm
let nr++
let base=nr
new "Add base <a>$base entry"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><x xmlns='urn:example:clixon'><y><a>$base</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>]]>]]>$'
let line+=10
# Variant check that only b,c
let nr++
new "7. 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>$base</a><b>1</b><c>1</c></y></x></config></edit-config></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf commit base" new "netconf commit base"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf insert b,c between end-points" # check complete
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>]]>]]>$' for op in begin validate complete commit end; do
checklog "$nr main_$op add: <b>1</b><c>1</c>" $line
new "netconf commit base" let line++
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><commit/></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$' checklog "$nr nacm_$op add: <b>1</b><c>1</c>" $line
let line++
checklog "transaction_log 4 validate add: <b>1</b><c>1</c>" done
if [ $BE -eq 0 ]; then if [ $BE -eq 0 ]; then
exit # BE exit # BE

View file

@ -198,11 +198,14 @@ main(int argc, char **argv)
xpath = argv[1]; xpath = argv[1];
else else
xpath = "/"; xpath = "/";
if (xmldb_get(h, db, xpath, &xt, NULL) < 0) if (xmldb_get(h, db, xpath, 1, &xt, NULL) < 0)
goto done; goto done;
clicon_xml2file(stdout, xt, 0, 0); clicon_xml2file(stdout, xt, 0, 0);
fprintf(stdout, "\n"); fprintf(stdout, "\n");
if (xt){
xml_free(xt);
xt = NULL;
}
} }
else if (strcmp(cmd, "mget")==0){ else if (strcmp(cmd, "mget")==0){
int nr; int nr;
@ -214,15 +217,17 @@ main(int argc, char **argv)
else else
xpath = "/"; xpath = "/";
for (i=0;i<nr;i++){ for (i=0;i<nr;i++){
if (xmldb_get(h, db, xpath, &xt, NULL) < 0) if (xmldb_get(h, db, xpath, 1, &xt, NULL) < 0)
goto done; goto done;
if (xt == NULL){ if (xt == NULL){
clicon_err(OE_DB, 0, "xt is NULL"); clicon_err(OE_DB, 0, "xt is NULL");
goto done; goto done;
} }
clicon_xml2file(stdout, xt, 0, 0); clicon_xml2file(stdout, xt, 0, 0);
xml_free(xt); if (xt){
xt = NULL; xml_free(xt);
xt = NULL;
}
} }
fprintf(stdout, "\n"); fprintf(stdout, "\n");
} }

View file

@ -80,9 +80,9 @@ module clixon-config {
} }
} }
} }
typedef xmldb_format{ typedef datastore_format{
description description
"Format of TEXT xml database format."; "Datastore format.";
type enumeration{ type enumeration{
enum xml{ enum xml{
description "Save and load xmldb as XML"; description "Save and load xmldb as XML";
@ -96,6 +96,24 @@ module clixon-config {
} }
} }
} }
typedef datastore_cache{
description
"XML configuration, ie running/candididate/ datastore cache behaviour.";
type enumeration{
enum nocache{
description "No cache always work directly with file";
}
enum cache{
description "Use in-memory cache.
Make copies when accessing internally.";
}
enum cache-zerocopy{
description "Use in-memory cache and dont copy.
Fastest but opens up for callbacks changing cache.";
}
}
}
typedef cli_genmodel_type{ typedef cli_genmodel_type{
description description
"How to generate CLI from YANG model, "How to generate CLI from YANG model,
@ -259,7 +277,8 @@ module clixon-config {
"If set, generate CLI specification for CLI completion of "If set, generate CLI specification for CLI completion of
loaded Yang modules. This CLI tree can be accessed in CLI loaded Yang modules. This CLI tree can be accessed in CLI
spec files using the tree reference syntax (eg @datamodel). spec files using the tree reference syntax (eg @datamodel).
See also CLICON_CLI_MODEL_TREENAME."; See also CLICON_CLI_MODEL_TREENAME.
(Consider boolean)";
} }
leaf CLICON_CLI_MODEL_TREENAME { leaf CLICON_CLI_MODEL_TREENAME {
type string; type string;
@ -272,7 +291,8 @@ module clixon-config {
leaf CLICON_CLI_GENMODEL_COMPLETION { leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32; type int32;
default 1; default 1;
description "Generate code for CLI completion of existing db symbols"; description "Generate code for CLI completion of existing db symbols.
Consider boolean type";
} }
leaf CLICON_CLI_GENMODEL_TYPE { leaf CLICON_CLI_GENMODEL_TYPE {
type cli_genmodel_type; type cli_genmodel_type;
@ -369,16 +389,15 @@ module clixon-config {
(see datastore/ and clixon_xml_db.[ch]) (see datastore/ and clixon_xml_db.[ch])
Obsolete: Merged with libclixon in 3.10"; Obsolete: Merged with libclixon in 3.10";
} }
leaf CLICON_XMLDB_CACHE { leaf CLICON_DATASTORE_CACHE {
type boolean; type datastore_cache;
default true; default cache;
description description
"XMLDB datatsore cache. "Clixon datastore cache behaviour. There are three values: no cache,
If set, XML candidate/running parsed tree is stored in memory cache with copy, or cache without copy.";
If not set, candidate/running is always accessed via disk.";
} }
leaf CLICON_XMLDB_FORMAT { leaf CLICON_XMLDB_FORMAT {
type xmldb_format; type datastore_format;
default xml; default xml;
description "XMLDB datastore format."; description "XMLDB datastore format.";
} }