System-only config

New `ca_system_only` backend callback for reading system-only data
New `CLICON_XMLDB_SYSTEM_ONLY_CONFIG` configuration option
API: Added `system_only` parameter to clixon_xml2file1()
Cleared running on commit and inited candidate on startup with system-only data
Added callback code in main example
This commit is contained in:
Olof hagsand 2024-10-11 11:59:49 +02:00
parent aec0a5fc3f
commit 3a656fac07
15 changed files with 580 additions and 78 deletions

View file

@ -104,5 +104,6 @@ int xmldb_print(clixon_handle h, FILE *f);
int xmldb_rename(clixon_handle h, const char *db, const char *newdb, const char *suffix);
int xmldb_populate(clixon_handle h, const char *db);
int xmldb_multi_upgrade(clixon_handle h, const char *db);
int xmldb_system_only_config(clixon_handle h, const char *xpath, cvec *nsc, cxobj **xret);
#endif /* _CLIXON_DATASTORE_H */

View file

@ -214,12 +214,12 @@ typedef int (plgreset_t)(clixon_handle h, const char *db);
* XXX: This callback may be replaced with a "dispatcher" type API in the future where the
* XPath binding is stricter, similar to the pagination API.
*
* @param[in] h Clixon handle
* @param[in] xpath Part of state requested
* @param[in] nsc XPath namespace context.
* @param[out] xtop XML tree where statedata is added
* @retval 0 OK
* @retval -1 Fatal error
* @param[in] h Clixon handle
* @param[in] xpath Part of state requested
* @param[in] nsc XPath namespace context.
* @param[out] xtop XML tree where data is added
* @retval 0 OK
* @retval -1 Fatal error
*
* @note The system will make an xpath check and filter out non-matching trees
* @note The system does not validate the xml, unless CLICON_VALIDATE_STATE_XML is set
@ -251,7 +251,13 @@ typedef int (plglockdb_t)(clixon_handle h, char *db, int lock, int id);
*/
typedef void *transaction_data;
/* Transaction callback */
/* Transaction callback
*
* @param[in] h Clixon handle
* @param[in] td Transaction data
* @retval 0 OK
* @retval -1 Error
*/
typedef int (trans_cb_t)(clixon_handle h, transaction_data td);
/*! Hook to override default prompt with explicit function
@ -396,6 +402,7 @@ struct clixon_plugin_api{
plgdaemon_t *cb_daemon; /* Plugin daemonized (always called) */
plgreset_t *cb_reset; /* Reset system status */
plgstatedata_t *cb_statedata; /* Provide state data XML from plugin */
plgstatedata_t *cb_system_only; /* Provide system-only config XML from plugin */
plglockdb_t *cb_lockdb; /* Database lock changed state */
trans_cb_t *cb_trans_begin; /* Transaction start */
trans_cb_t *cb_trans_validate; /* Transaction validation */
@ -419,6 +426,7 @@ struct clixon_plugin_api{
#define ca_daemon u.cau_backend.cb_daemon
#define ca_reset u.cau_backend.cb_reset
#define ca_statedata u.cau_backend.cb_statedata
#define ca_system_only u.cau_backend.cb_system_only
#define ca_lockdb u.cau_backend.cb_lockdb
#define ca_trans_begin u.cau_backend.cb_trans_begin
#define ca_trans_validate u.cau_backend.cb_trans_validate
@ -508,6 +516,8 @@ int clixon_plugin_datastore_upgrade_all(clixon_handle h, const char *db, cxobj *
int clixon_plugin_yang_mount_one(clixon_plugin_t *cp, clixon_handle h, cxobj *xt, int *config, validate_level *vl, cxobj **yanglib);
int clixon_plugin_yang_mount_all(clixon_handle h, cxobj *xt, int *config, validate_level *vl, cxobj **yanglib);
int clixon_plugin_system_only_all(clixon_handle h, yang_stmt *yspec, cvec *nsc, char *xpath, cxobj **xtop);
int clixon_plugin_yang_patch_one(clixon_plugin_t *cp, clixon_handle h, yang_stmt *ymod);
int clixon_plugin_yang_patch_all(clixon_handle h, yang_stmt *ymod);

View file

@ -45,7 +45,7 @@
*/
int clixon_xml2file1(FILE *f, cxobj *xn, int level, int pretty, char *prefix,
clicon_output_cb *fn, int skiptop, int autocliext, withdefaults_type wdef,
int multi);
int multi, int system_only);
int clixon_xml2file(FILE *f, cxobj *xn, int level, int pretty, char *prefix, clicon_output_cb *fn, int skiptop, int autocliext);
int clixon_xml2file_multi(clixon_handle h, const char *db, cxobj *xn, int level, int pretty,
char *prefix, clicon_output_cb *fn, int skiptop, int autocliext,

View file

@ -1021,3 +1021,40 @@ xmldb_multi_upgrade(clixon_handle h,
free(tofile);
return retval;
}
/*! Get system-only config data by calling user callback
*
* @param[in] h Clixon handle
* @param[in] xpath XPath selection, may be used to filter early
* @param[in] nsc XML Namespace context for xpath
* @param[in,out] xret Existing XML tree, merge x into this, or rpc-error
* @retval 1 OK
* @retval 0 Statedata callback failed (error in xret)
* @retval -1 Error (fatal)
*/
int
xmldb_system_only_config(clixon_handle h,
const char *xpath,
cvec *nsc,
cxobj **xret)
{
int retval = -1;
yang_stmt *yspec;
int ret;
clixon_debug(CLIXON_DBG_BACKEND, "");
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clixon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if ((ret = clixon_plugin_system_only_all(h, yspec, nsc, (char*)xpath, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
retval = 1; /* OK */
done:
return retval;
fail:
retval = 0;
goto done;
}

View file

@ -886,6 +886,11 @@ xmldb_get_cache(clixon_handle h,
if (xml_apply(x1t, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
}
if (clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG") &&
strcmp(db, "running") == 0){
if (xmldb_system_only_config(h, xpath?xpath:"/", nsc, &x1t) < 0)
goto done;
}
/* If empty NACM config, then disable NACM if loaded
*/
if (clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){

View file

@ -1560,7 +1560,7 @@ xmldb_multi_write_applyfn(cxobj *x,
goto done;
}
/* Dont recurse multi-file yet */
if (clixon_xml2file1(fsub, x, 0, mw->mw_pretty, NULL, fprintf, 1, 0, mw->mw_wdef, 0) < 0)
if (clixon_xml2file1(fsub, x, 0, mw->mw_pretty, NULL, fprintf, 1, 0, mw->mw_wdef, 0, 0) < 0)
goto done;
}
retval = 2; /* Locally abort */
@ -1627,7 +1627,9 @@ xmldb_dump(clixon_handle h,
}
switch (format){
case FORMAT_XML:
if (clixon_xml2file1(f, xt, 0, pretty, NULL, fprintf, 0, 0, wdef, multi) < 0)
if (clixon_xml2file1(f, xt, 0, pretty, NULL, fprintf, 0, 0, wdef, multi,
clicon_option_bool(h, "CLICON_XMLDB_SYSTEM_ONLY_CONFIG")
) < 0)
goto done;
if (multi){
mw.mw_h = h;

View file

@ -68,6 +68,11 @@
#include "clixon_options.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_default.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_yang_module.h"
#include "clixon_netconf_lib.h"
#include "clixon_validate.h"
@ -927,6 +932,166 @@ clixon_plugin_yang_mount_all(clixon_handle h,
return retval;
}
/*! Call single backend statedata callback
*
* Create an xml tree (xret) for one callback only on the form:
* <config>...</config>,
* call a user supplied function (ca_system_only) which can do two things:
* - Fill in config XML in the tree and return 0
* - Call cli_error() and return -1
* In the former case, this function returns the config XML tree to the caller (which
* typically merges the tree with other config trees).
* In the latter error case, this function returns 0 (invalid) to the caller with no tree
* If a fatal error occurs in this function, -1 is returned.
*
* @param[in] cp Plugin handle
* @param[in] h clicon handle
* @param[in] nsc namespace context for xpath
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xp If retval=1, tree created and returned: <config>...
* @retval 1 OK if callback found (and called) xret is set
* @retval 0 Callback failed. no XML tree returned
* @retval -1 Fatal error
*/
static int
clixon_plugin_system_only_one(clixon_plugin_t *cp,
clixon_handle h,
cvec *nsc,
char *xpath,
cxobj **xp)
{
int retval = -1;
plgstatedata_t *fn; /* Plugin statedata fn */
cxobj *x = NULL;
void *wh = NULL;
if ((fn = clixon_plugin_api_get(cp)->ca_system_only) != NULL){
if ((x = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
wh = NULL;
if (clixon_resource_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
goto done;
if (fn(h, nsc, xpath, x) < 0){
if (clixon_resource_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
goto done;
if (clixon_err_category() < 0)
clixon_log(h, LOG_WARNING, "%s: Internal error: Stateonly callback in plugin: %s returned -1 but did not make a clixon_err call",
__FUNCTION__, clixon_plugin_name_get(cp));
goto fail; /* Dont quit here on user callbacks */
}
if (clixon_resource_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
goto done;
}
if (xp && x){
*xp = x;
x = NULL;
}
retval = 1;
done:
if (x)
xml_free(x);
return retval;
fail:
retval = 0;
goto done;
}
/*! Go through all backend system-only callbacks and collect config data
*
* @param[in] h clicon handle
* @param[in] yspec Yang spec
* @param[in] nsc Namespace context
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xret Config XML tree is merged with existing tree.
* @retval 1 OK
* @retval 0 Callback failed (xret set with netconf-error)
* @retval -1 Error
* @note xret can be replaced in this function
*/
int
clixon_plugin_system_only_all(clixon_handle h,
yang_stmt *yspec,
cvec *nsc,
char *xpath,
cxobj **xret)
{
int retval = -1;
int ret;
cxobj *x = NULL;
clixon_plugin_t *cp = NULL;
cbuf *cberr = NULL;
cxobj *xerr = NULL;
clixon_debug(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, "");
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
if ((ret = clixon_plugin_system_only_one(cp, h, nsc, xpath, &x)) < 0)
goto done;
if (ret == 0){
if ((cberr = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* error reason should be in clixon_err_reason */
cprintf(cberr, "Internal error, system-only callback in plugin %s returned invalid XML: %s",
clixon_plugin_name_get(cp), clixon_err_reason());
if (netconf_operation_failed_xml(&xerr, "application", cbuf_get(cberr)) < 0)
goto done;
xml_free(*xret);
*xret = xerr;
xerr = NULL;
goto fail;
}
if (x == NULL)
continue;
if (xml_child_nr(x) == 0){
xml_free(x);
x = NULL;
continue;
}
clixon_debug_xml(CLIXON_DBG_BACKEND | CLIXON_DBG_DETAIL, x, "%s SYSTEM-ONLY:", clixon_plugin_name_get(cp));
/* XXX: ret == 0 invalid yang binding should be handled as internal error */
if ((ret = xml_bind_yang(h, x, YB_MODULE, yspec, &xerr)) < 0)
goto done;
if (ret == 0){
if (clixon_netconf_internal_error(xerr,
". Internal error, system-only callback returned invalid XML from plugin: ",
clixon_plugin_name_get(cp)) < 0)
goto done;
xml_free(*xret);
*xret = xerr;
xerr = NULL;
goto fail;
}
if (xml_sort_recurse(x) < 0)
goto done;
/* Remove global defaults and empty non-presence containers */
if (xml_default_nopresence(x, 2, 0) < 0)
goto done;
if (xpath_first(x, nsc, "%s", xpath) != NULL){
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (x){
xml_free(x);
x = NULL;
}
} /* while plugin */
retval = 1;
done:
if (xerr)
xml_free(xerr);
if (cberr)
cbuf_free(cberr);
if (x)
xml_free(x);
return retval;
fail:
retval = 0;
goto done;
}
/*! Call plugin YANG schema patch
*
* @param[in] cp Plugin handle

View file

@ -203,6 +203,7 @@ xml2output_wdef(cxobj *x,
* @param[in] autocliext How to handle autocli extensions: 0: ignore 1: follow
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @param[in] multi Multi-file split datastore, see CLICON_XMLDB_MULTI
* @param[in] system_only Enable checks for system-only-config extension
* @retval 0 OK
* @retval -1 Error
* One can use clixon_xml2cbuf to get common code, but using fprintf is
@ -223,30 +224,32 @@ xml2file_recurse(FILE *f,
clicon_output_cb *fn,
int autocliext,
withdefaults_type wdef,
int multi)
int multi,
int system_only)
{
int retval = -1;
char *name;
char *namespace;
cxobj *xc;
int hasbody;
int haselement;
char *val;
char *encstr = NULL; /* xml encoded string */
int exist;
yang_stmt *y;
int level1;
int tag = 0;
int ret;
int subfile = 0; /* File is split into subfile */
char *xpath = NULL;
char *hexstr = NULL;
int retval = -1;
char *name;
char *namespace;
cxobj *xc;
int hasbody;
int haselement;
char *val;
char *encstr = NULL; /* xml encoded string */
int exist;
yang_stmt *y;
int level1;
int tag = 0;
int subfile = 0; /* File is split into subfile */
char *xpath = NULL;
char *hexstr = NULL;
int ret;
if (x == NULL)
goto ok;
y = xml_spec(x);
/* Check if system-only, then do not write to datastore */
if (y != NULL) {
/* Check if system-only, then do not write to datastore
*/
if (y != NULL && system_only){
exist = 0;
if (yang_extension_value(y, "system-only-config", CLIXON_LIB_NS, &exist, NULL) < 0)
goto done;
@ -302,7 +305,7 @@ xml2file_recurse(FILE *f,
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
switch (xml_type(xc)){
case CX_ATTR:
if (xml2file_recurse(f, xc, level+1, pretty, prefix, fn, autocliext, wdef, multi) < 0)
if (xml2file_recurse(f, xc, level+1, pretty, prefix, fn, autocliext, wdef, multi, system_only) < 0)
goto done;
break;
case CX_BODY:
@ -360,7 +363,7 @@ xml2file_recurse(FILE *f,
}
if (xml_type(xc) != CX_ATTR && !subfile)
if (xml2file_recurse(f, xc, level+1, pretty, prefix,
fn, autocliext, wdef, multi) <0)
fn, autocliext, wdef, multi, system_only) <0)
goto done;
if (xa){
if (xml_purge(xa) < 0)
@ -412,6 +415,7 @@ xml2file_recurse(FILE *f,
* @param[in] autocliext How to handle autocli extensions: 0: ignore 1: follow
* @param[in] wdef With-defaults parameter, default is WITHDEFAULTS_REPORT_ALL
* @param[in] multi Multi-file split datastore, see CLICON_XMLDB_MULTI
* @param[in] system_only Enable checks for system-only-config extension
* @retval 0 OK
* @retval -1 Error
* @see clixon_xml2cbuf print to a cbuf string
@ -428,7 +432,8 @@ clixon_xml2file1(FILE *f,
int skiptop,
int autocliext,
withdefaults_type wdef,
int multi)
int multi,
int system_only)
{
int retval = 1;
cxobj *xc;
@ -438,11 +443,11 @@ clixon_xml2file1(FILE *f,
if (skiptop){
xc = NULL;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL)
if (xml2file_recurse(f, xc, level, pretty, prefix, fn, autocliext, wdef, multi) < 0)
if (xml2file_recurse(f, xc, level, pretty, prefix, fn, autocliext, wdef, multi, system_only) < 0)
goto done;
}
else {
if (xml2file_recurse(f, xn, level, pretty, prefix, fn, autocliext, wdef, multi) < 0)
if (xml2file_recurse(f, xn, level, pretty, prefix, fn, autocliext, wdef, multi, system_only) < 0)
goto done;
}
retval = 0;
@ -478,7 +483,7 @@ clixon_xml2file(FILE *f,
int skiptop,
int autocliext)
{
return clixon_xml2file1(f, xn, level, pretty, prefix, fn, skiptop, autocliext, 0, 0);
return clixon_xml2file1(f, xn, level, pretty, prefix, fn, skiptop, autocliext, 0, 0, 0);
}
/*! Print an XML tree structure to an output stream
@ -493,7 +498,7 @@ int
xml_print(FILE *f,
cxobj *x)
{
return xml2file_recurse(f, x, 0, 1, NULL, fprintf, 0, WITHDEFAULTS_REPORT_ALL, 0);
return xml2file_recurse(f, x, 0, 1, NULL, fprintf, 0, WITHDEFAULTS_REPORT_ALL, 0, 0);
}
/*! Dump cxobj structure with pointers and flags for debugging, internal function
@ -541,7 +546,7 @@ xml_dump(FILE *f,
return xml_dump1(f, x, 0);
}
/*! Internal: print XML tree structure to a cligen buffer and encode chars "<>&"
/*! Internal: print XML tree structure to a cligen buffer and encode chars "<>&"
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] xn Clixon xml tree
@ -724,14 +729,14 @@ xml2cbuf_recurse(cbuf *cb,
* @see clixon_xml2file to file, which is faster
*/
int
clixon_xml2cbuf1(cbuf *cb,
cxobj *xn,
int level,
int pretty,
char *prefix,
int32_t depth,
int skiptop,
withdefaults_type wdef)
clixon_xml2cbuf1(cbuf *cb,
cxobj *xn,
int level,
int pretty,
char *prefix,
int32_t depth,
int skiptop,
withdefaults_type wdef)
{
int retval = -1;
cxobj *xc;