* New backend startup and upgrade support, see [doc/startup.md] for details

* Datastore files contain RFC7895 module-state information
This commit is contained in:
Olof hagsand 2019-02-13 11:35:49 +01:00
parent 28bd698968
commit 560110b4e8
44 changed files with 1595 additions and 631 deletions

View file

@ -74,6 +74,7 @@ APPSRC += backend_socket.c
APPSRC += backend_client.c
APPSRC += backend_commit.c
APPSRC += backend_plugin.c
APPSRC += backend_startup.c
APPOBJ = $(APPSRC:.c=.o)
# Accessible from plugin

View file

@ -64,8 +64,8 @@
#include <clixon/clixon.h>
#include "clixon_backend_handle.h"
#include "backend_commit.h"
#include "backend_plugin.h"
#include "backend_commit.h"
#include "backend_client.h"
#include "backend_handle.h"
@ -208,7 +208,7 @@ from_client_get_config(clicon_handle h,
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
if (xmldb_get(h, db, xpath, 1, &xret) < 0){
if (xmldb_get(h, db, xpath, 1, &xret, NULL) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done;
goto ok;
@ -335,7 +335,7 @@ client_statedata(clicon_handle h,
if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
goto done;
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
if ((retval = yang_modules_state_get(h, yspec, xret)) != 0)
if ((retval = yang_modules_state_get(h, yspec, 0, xret)) != 0)
goto done;
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
goto done;
@ -391,7 +391,7 @@ from_client_get(clicon_handle h,
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
/* Get config */
if (xmldb_get(h, "running", xpath, 0, &xret) < 0){
if (xmldb_get(h, "running", xpath, 0, &xret, NULL) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done;
goto ok;

View file

@ -140,7 +140,9 @@ generic_validate(yang_spec *yspec,
goto done;
}
/*! Common code of candidate_validate and candidate_commit
/*! Validate a candidate db and comnpare to running
* Get both source and dest datastore, validate target, compute diffs
* and call application callback validations.
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @retval -1 Error - or validation failed (but cbret not set)
@ -167,10 +169,10 @@ validate_common(clicon_handle h,
}
/* 2. Parse xml trees
* This is the state we are going from */
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
if (xmldb_get(h, "running", "/", 1, &td->td_src, NULL) < 0)
goto done;
/* This is the state we are going to */
if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0)
if (xmldb_get(h, candidate, "/", 1, &td->td_target, NULL) < 0)
goto done;
/* Validate the target state. It is not completely clear this should be done
@ -244,6 +246,90 @@ validate_common(clicon_handle h,
goto done;
}
/*! Validate a candidate db and comnpare to running XXX Experimental
* Get both source and dest datastore, validate target, compute diffs
* and call application callback validations.
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
* 1. Parse startup XML (or JSON)
* 2. If syntax failure, call startup-cb(ERROR), copy failsafe db to
* candidate and commit. Done
* 3. Check yang module versions between backend and init config XML. (msdiff)
* 4. Validate startup db. (valid)
* 5. If valid fails, call startup-cb(Invalid, msdiff), keep startup in candidate and commit failsafe db. Done.
* 6. Call startup-cb(OK, msdiff) and commit.
*/
int
startup_validate(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
cxobj *xms = NULL;
transaction_data_t *td = NULL;
/* Handcraft a transition with only target and add trees */
if ((td = transaction_new()) == NULL)
goto done;
/* 2. Parse xml trees
* This is the state we are going to
* Note: xmsdiff contains non-matching modules
*/
if (xmldb_get(h, db, "/", 1, &td->td_target, &xms) < 0)
goto done;
if (xms && clixon_plugin_upgrade(h, xms) < 0)
goto done;
/* Handcraft transition with with only add tree */
if (cxvec_append(td->td_target, &td->td_avec, &td->td_alen) < 0)
goto done;
/* 4. Call plugin transaction start callbacks */
if (plugin_transaction_begin(h, td) < 0)
goto done;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, 0, "Yang spec not set");
goto done;
}
/* 5. Make generic validation on all new or changed data.
Note this is only call that uses 3-values */
if ((ret = generic_validate(yspec, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail; /* STARTUP_INVALID */
/* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0)
goto done;
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
retval = 1;
done:
if (td)
transaction_free(td);
if (xms)
xml_free(xms);
return retval;
fail: /* cbret should be set */
if (cbuf_len(cbret)==0){
clicon_err(OE_CFG, EINVAL, "Validation fail but cbret not set");
goto done;
}
retval = 0;
goto done;
}
/*! Do a diff between candidate and running, then start a commit transaction
*
* The code reverts changes if the commit fails. But if the revert

View file

@ -43,6 +43,7 @@
int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
int startup_validate(clicon_handle h, char *db, cbuf *cbret);
int candidate_commit(clicon_handle h, char *db, cbuf *cbret);
#endif /* _BACKEND_COMMIT_H_ */

View file

@ -68,9 +68,10 @@
#include "clixon_backend_handle.h"
#include "backend_socket.h"
#include "backend_client.h"
#include "backend_commit.h"
#include "backend_plugin.h"
#include "backend_commit.h"
#include "backend_handle.h"
#include "backend_startup.h"
/* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:l:d:p:b:Fza:u:P:1s:c:g:y:x:o:"
@ -128,84 +129,6 @@ backend_sig_term(int arg)
clicon_exit_set(); /* checked in event_loop() */
}
/*! usage
*/
static void
usage(clicon_handle h,
char *argv0)
{
char *plgdir = clicon_backend_dir(h);
char *confsock = clicon_sock(h);
char *confpid = clicon_backend_pidfile(h);
char *group = clicon_sock_group(h);
fprintf(stderr, "usage:%s <options>*\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n"
"\t-f <file>\tCLICON config file\n"
"\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
"\t-b <dir>\tSpecify XMLDB database directory\n"
"\t-F\t\tRun in foreground, do not run as daemon\n"
"\t-z\t\tKill other config daemon and exit\n"
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)(default: %s)\n"
"\t-P <file>\tPid filename (default: %s)\n"
"\t-1\t\tRun once and then quit (dont wait for events)\n"
"\t-s <mode>\tSpecify backend startup mode: none|startup|running|init)\n"
"\t-c <file>\tLoad extra xml configuration, but don't commit.\n"
"\t-g <group>\tClient membership required to this group (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-x <plugin>\tXMLDB plugin\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
confpid ? confpid : "none",
group ? group : "none"
);
exit(-1);
}
static int
db_reset(clicon_handle h,
char *db)
{
if (xmldb_exists(h, db) == 1 && xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1;
if (xmldb_create(h, db) < 0)
return -1;
return 0;
}
/*! Merge db1 into db2 without commit
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
db_merge(clicon_handle h,
const char *db1,
const char *db2,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
/* Get data as xml from db1 */
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
goto done;
/* Merge xml into db2. Without commit */
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Create backend server socket and register callback
* @param[in] h Clicon handle
* @retval s Server socket file descriptor (see socket(2))
@ -298,256 +221,88 @@ nacm_load_external(clicon_handle h)
return retval;
}
/*! Merge xml in filename into database
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
load_extraxml(clicon_handle h,
char *filename,
const char *db,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
int fd = -1;
if (filename == NULL)
return 1;
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
if (xml_parse_file(fd, "</config>", NULL, &xt) < 0)
goto done;
/* Replace parent w first child */
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
retval = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (fd != -1)
close(fd);
if (xt)
xml_free(xt);
return retval;
}
/*! Clixon none startup modes Do not touch running state
*/
static int
startup_mode_none(clicon_handle h)
{
int retval = -1;
/* If it is not there, create candidate from running */
if (xmldb_exists(h, "candidate") != 1)
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Clixon init startup modes Start with a completely clean running state
*/
static int
startup_mode_init(clicon_handle h)
{
int retval = -1;
/* Reset running, regardless */
if (db_reset(h, "running") < 0)
goto done;
/* If it is not there, create candidate from running */
if (xmldb_exists(h, "candidate") != 1)
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Clixon running startup mode: Commit running db configuration into running
/*! Given a retval, transform to status or fatal error
*
OK:
copy reset commit merge
running----+ |--------------------+--------+------>
\ / /
candidate +--------------------+ /
/
tmp |-------+-----+------------+---|
reset extra file
COMMIT ERROR:
copy reset copy
running----+ |--------------------+------> EXIT
\ /
candidate +--------------------+
* @note: if commit fails, copy candidate to running and exit
* @param[in] ret Return value from xml validation function
* @param[out] status Transform status according to rules below
* @retval 0 OK, status set
* @retval -1 Fatal error outside scope of startup_status
* Transformation rules:
* 1) retval -1 assume clicon_errno/suberrno set. Special case from xml parser
* is clicon_suberrno = XMLPARSE_ERRNO which assumes an XML (non-fatal) parse
* error which translates to -> STARTUP_ERR
* All other error cases translates to fatal error
* 2) retval 0 is xml validation fails -> STARTUP_INVALID
* 3) retval 1 is OK -> STARTUP_OK
* 4) any other retval translates to fatal error
*/
static int
startup_mode_running(clicon_handle h,
char *extraxml_file)
ret2status(int ret,
enum startup_status *status)
{
int retval = -1;
cbuf *cbret = NULL;
int retval = -1;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to candidate for later commit */
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
/* Clear tmp db */
if (db_reset(h, "tmp") < 0)
goto done;
/* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0)
goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto fail;
/* Clear running db */
if (db_reset(h, "running") < 0)
goto done;
/* Commit original running. Assume -1 is validate fail */
if (candidate_commit(h, "candidate", cbret) < 1)
goto fail;
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running", cbret) < 1)
goto fail;
switch (ret){
case -1:
if (clicon_suberrno != XMLPARSE_ERRNO)
goto done;
clicon_err_reset();
*status = STARTUP_ERR;
break;
case 0:
*status = STARTUP_INVALID;
break;
case 1:
*status = STARTUP_OK;
break;
default:
clicon_err(OE_CFG, EINVAL, "No such retval %d", retval);
} /* switch */
retval = 0;
done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
fail:
/* (1) We cannot differentiate between fatal errors and validation
* failures
* (2) If fatal error, we should exit
* (3) If validation fails we cannot continue. How could we?
* (4) Need to restore the running db since we destroyed it above
*/
if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
/* Reinstate original */
if (xmldb_copy(h, "candidate", "running") < 0)
goto done;
goto done;
}
/*! Clixon startup startup mode: Commit startup configuration into running state
backup +--------------------|
copy / reset commit merge
running |-+----|--------------------+-----+------>
/ /
startup -------------------------+--> /
/
tmp -----|-------+-----+---------+--|
reset extra file
COMMIT ERROR:
backup +------------------------+--|
copy / reset copy \
running |-+----|--------------------+---+------->EXIT
error /
startup -------------------------+--|
* @note: if commit fails, copy backup to commit and exit
/*! usage
*/
static int
startup_mode_startup(clicon_handle h,
char *extraxml_file)
static void
usage(clicon_handle h,
char *argv0)
{
int retval = -1;
cbuf *cbret = NULL;
char *plgdir = clicon_backend_dir(h);
char *confsock = clicon_sock(h);
char *confpid = clicon_backend_pidfile(h);
char *group = clicon_sock_group(h);
/* Create return buffer for netconf xml errors */
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to backup */
if (xmldb_copy(h, "running", "backup") < 0)
goto done;
/* If startup does not exist, clear it */
if (xmldb_exists(h, "startup") != 1) /* diff */
if (xmldb_create(h, "startup") < 0) /* diff */
return -1;
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
/* Clear tmp db */
if (db_reset(h, "tmp") < 0)
goto done;
/* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0)
goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto fail;
/* Clear running db */
if (db_reset(h, "running") < 0)
goto done;
fprintf(stderr, "usage:%s <options>*\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n"
"\t-f <file>\tCLICON config file\n"
"\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
"\t-b <dir>\tSpecify XMLDB database directory\n"
"\t-F\t\tRun in foreground, do not run as daemon\n"
"\t-z\t\tKill other config daemon and exit\n"
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)(default: %s)\n"
"\t-P <file>\tPid filename (default: %s)\n"
"\t-1\t\tRun once and then quit (dont wait for events)\n"
"\t-s <mode>\tSpecify backend startup mode: none|startup|running|init)\n"
"\t-c <file>\tLoad extra xml configuration, but don't commit.\n"
"\t-g <group>\tClient membership required to this group (default: %s)\n"
/* Commit startup */
if (candidate_commit(h, "startup", cbret) < 1) /* diff */
goto fail;
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running", cbret) < 1)
goto fail;
retval = 0;
done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "backup") < 0)
goto done;
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
fail:
/* We cannot differentiate between fatal errors and validation
* failures
* In both cases we copy back the original running and quit
*/
if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
if (xmldb_copy(h, "backup", "running") < 0)
goto done;
goto done;
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-x <plugin>\tXMLDB plugin\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
confpid ? confpid : "none",
group ? group : "none"
);
exit(-1);
}
int
@ -579,6 +334,9 @@ main(int argc,
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
char *str;
int ss = -1; /* server socket */
cbuf *cbret = NULL; /* startup cbuf if invalid */
enum startup_status status = STARTUP_ERR; /* Startup status */
int ret;
/* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -789,7 +547,7 @@ main(int argc,
}
if (group_name2gid(config_group, NULL) < 0){
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n"
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n" /* \n required here due to multi-line log */
"The config demon requires a valid group to create a server UNIX socket\n"
"Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
"or create the group and add the user to it. On linux for example:"
@ -808,7 +566,7 @@ main(int argc,
stream_publish_init() < 0)
goto done;
if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){
clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n");
clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).");
goto done;
}
if (xmldb_plugin_load(h, xmldb_plugin) < 0)
@ -869,10 +627,14 @@ main(int argc,
goto done;
if (xmldb_setopt(h, "nacm_xtree", (void*)clicon_nacm_ext(h)) < 0)
goto done;
/* Save modules state of the backend (server). Compare with startup XML */
if (startup_module_state(h, yspec) < 0)
goto done;
/* Startup mode needs to be defined, */
startup_mode = clicon_startup_mode(h);
if (startup_mode == -1){
clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.\n");
clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.");
goto done;
}
/* Init running db if it is not there
@ -880,30 +642,60 @@ main(int argc,
if (xmldb_exists(h, "running") != 1)
if (xmldb_create(h, "running") < 0)
return -1;
switch (startup_mode){
case SM_NONE:
if (startup_mode_none(h) < 0)
goto done;
break;
case SM_INIT: /* -I */
if (startup_mode_init(h) < 0)
goto done;
break;
case SM_RUNNING: /* -CIr */
if (startup_mode_running(h, extraxml_file) < 0)
goto done;
break;
case SM_STARTUP: /* startup configuration */
if (startup_mode_startup(h, extraxml_file) < 0)
goto done;
break;
/* If startup fails, lib functions report invalidation info in a cbuf */
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
switch (startup_mode){
case SM_INIT: /* Scratch running and start from empty */
/* [Delete and] create running db */
if (startup_db_reset(h, "running") < 0)
goto done;
case SM_NONE: /* Fall through *
* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
status = STARTUP_OK;
break;
case SM_RUNNING: /* Use running as startup */
/* Copy original running to startup and treat as startup */
if (xmldb_copy(h, "running", "startup") < 0)
goto done;
case SM_STARTUP: /* Fall through */
/* Load and commit from startup */
ret = startup_mode_startup(h, cbret);
if (ret2status(ret, &status) < 0)
goto done;
}
if (status != STARTUP_OK){
if (startup_failsafe(h) < 0){
goto done;
}
}
/* Initiate the shared candidate. */
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
/* Set startup status */
if (clicon_startup_status_set(h, status) < 0)
goto done;
/* Call backend plugin_start with user -- options */
if (plugin_start_useroptions(h, argv0, argc, argv) <0)
goto done;
/* Merge extra XML from file and reset function to running (optional)
* XXX: rm && startup_mode != SM_INIT
*/
if (status == STARTUP_OK && startup_mode != SM_NONE && startup_mode != SM_INIT){
if ((ret = startup_extraxml(h, extraxml_file, cbret)) < 0)
goto done;
if (ret2status(ret, &status) < 0)
goto done;
}
if (once)
goto done;

View file

@ -157,6 +157,31 @@ clixon_plugin_statedata(clicon_handle h,
return retval;
}
/*! Call configuration upgrade routines in backend plugins
* @param[in] h Clicon handle
* @param[in] xms XML tree of module state differences
* @retval 0 OK
* @retval -1 Error in one (first) of user callbacks
*/
int
clixon_plugin_upgrade(clicon_handle h,
cxobj *xmodst)
{
int retval = -1;
clixon_plugin *cp = NULL;
upgrade_cb_t *fn; /* Plugin configuration upgrade fn */
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_upgrade) == NULL)
continue;
if (fn(h, xmodst) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Create and initialize transaction */
transaction_data_t *
transaction_new(void)

View file

@ -72,7 +72,7 @@ int backend_plugin_initiate(clicon_handle h);
int clixon_plugin_reset(clicon_handle h, char *db);
int clixon_plugin_statedata(clicon_handle h, yang_spec *yspec, char *xpath, cxobj **xtop);
int clixon_plugin_upgrade(clicon_handle h, cxobj *xmodst);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);

View file

@ -152,7 +152,7 @@ config_socket_init_unix(clicon_handle h,
return -1;
#if 0
if (gid == 0)
clicon_log(LOG_WARNING, "%s: No such group: %s\n", __FUNCTION__, config_group);
clicon_log(LOG_WARNING, "%s: No such group: %s", __FUNCTION__, config_group);
#endif
/* create unix socket */
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {

View file

@ -0,0 +1,340 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_backend_transaction.h"
#include "backend_plugin.h"
#include "backend_handle.h"
#include "backend_commit.h"
#include "backend_startup.h"
/*! Create an XML database. If it exists already, delete it before creating
* @param[in] h Clixon handle
* @param[in] db Symbolic database name, eg "candidate", "running"
*/
int
startup_db_reset(clicon_handle h,
char *db)
{
if (xmldb_exists(h, db) == 1){
if (xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1;
}
if (xmldb_create(h, db) < 0)
return -1;
return 0;
}
/*! Merge db1 into db2 without commit
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
db_merge(clicon_handle h,
const char *db1,
const char *db2,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
/* Get data as xml from db1 */
if (xmldb_get(h, (char*)db1, NULL, 1, &xt, NULL) < 0)
goto done;
/* Merge xml into db2. Without commit */
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Clixon startup startup mode: Commit startup configuration into running state
* @param[in] h Clixon handle
* @param[out] cbret If status is invalid contains error message
* @retval -1 Error
* @retval 0 Validation failed
* @retval 1 OK
OK:
reset
running |--------+------------> RUNNING
parse validate OK / commit
startup -------+--+-------+------------+
INVALID (requires manual edit of candidate)
failsafe ----------------------+
reset \ commit
running |-------+---------------> RUNNING FAILSAFE
parse validate fail
startup ---+-------------------------------------> INVALID XML
ERR: (requires repair of startup) NYI
failsafe ----------------------+
reset \ commit
running |-------+---------------> RUNNING FAILSAFE
parse fail
startup --+-------------------------------------> BROKEN XML
* @note: if commit fails, copy factory to running
*/
int
startup_mode_startup(clicon_handle h,
cbuf *cbret)
{
int retval = -1;
int ret;
char *db = "startup";
/* [Delete and] create running db */
if (startup_db_reset(h, "running") < 0)
goto done;
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
/* If startup does not exist, create it empty */
if (xmldb_exists(h, db) != 1){ /* diff */
if (xmldb_create(h, db) < 0) /* diff */
return -1;
}
if ((ret = startup_validate(h, db, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Commit startup */
if (candidate_commit(h, db, cbret) < 1) /* diff */
goto fail;
if (ret == 0) /* shouldnt happen (we already validate) */
goto fail;
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Merge xml in filename into database
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
load_extraxml(clicon_handle h,
char *filename,
const char *db,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
int fd = -1;
if (filename == NULL)
return 1;
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
if (xml_parse_file(fd, "</config>", NULL, &xt) < 0)
goto done;
/* Replace parent w first child */
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
retval = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (fd != -1)
close(fd);
if (xt)
xml_free(xt);
return retval;
}
/*! Load extra XML via file and/or reset callback, and merge with current
* An application can add extra XML either via the -c <file> option or
* via the .ca_reset callback. This XML is "merged" into running, that is,
* it does not trigger validation calbacks.
* The function uses an extra "tmp" database, loads the file to it, and calls
* the reset function on it.
* @param[in] h Clicon handle
* @param[in] file (Optional) extra xml file
* @param[out] status Startup status
* @param[out] cbret If status is invalid contains error message
* @retval -1 Error
* @retval 0 Validation failed
* @retval 1 OK
running -----------------+----+------>
reset loadfile / merge
tmp |-------+-----+-----+
reset extrafile
*/
int
startup_extraxml(clicon_handle h,
char *file,
cbuf *cbret)
{
int retval = -1;
char *db = "tmp";
int ret;
/* Clear tmp db */
if (startup_db_reset(h, db) < 0)
goto done;
/* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, db) < 0)
goto done;
/* Extra XML can also be added via file */
if (file){
/* Parse and load file into tmp db */
if ((ret = load_extraxml(h, file, db, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* Validate tmp (unless empty?) */
if ((ret = startup_validate(h, db, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Merge tmp into running (no commit) */
if ((ret = db_merge(h, db, "running", cbret)) < 0)
goto fail;
if (ret == 0)
goto fail;
retval = 1;
done:
if (xmldb_delete(h, "tmp") != 0 && errno != ENOENT)
return -1;
return retval;
fail:
retval = 0;
goto done;
}
/*! Reset running and start in failsafe mode. If no failsafe then quit.
Typically done when startup status is not OK so
failsafe ----------------------+
reset \ commit
running |-------+---------------> RUNNING FAILSAFE
*/
int
startup_failsafe(clicon_handle h)
{
int retval = -1;
int ret;
char *db = "failsafe";
cbuf *cbret = NULL;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (startup_db_reset(h, "running") < 0)
goto done;
if ((ret = xmldb_exists(h, db)) < 0)
goto done;
if (ret == 0){ /* No it does not exist, fail */
clicon_err(OE_DB, 0, "No failsafe database");
goto done;
}
if ((ret = candidate_commit(h, db, cbret)) < 0) /* diff */
goto done;
if (ret == 0){
clicon_err(OE_DB, 0, "Failsafe database validation failed %s", cbuf_get(cbret));
goto done;
}
retval = 0;
done:
if (cbret)
cbuf_free(cbret);
return retval;
}
/*! Init modules state of the backend (server). To compare with startup XML
* Set the modules state as setopt to the datastore module.
*/
int
startup_module_state(clicon_handle h,
yang_spec *yspec)
{
int retval = -1;
cxobj *x = NULL;
if (yang_modules_state_get(h, yspec, 1, &x) < 0)
goto done;
if (x){
if (xml_rootchild(x, 0, &x) < 0)
goto done;
if (xmldb_setopt(h, "modules_state", (void*)x) < 0)
goto done;
}
retval = 0;
done:
return retval;
}

View file

@ -0,0 +1,49 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifndef _BACKEND_STARTUP_H_
#define _BACKEND_STARTUP_H_
/*
* Prototypes
*/
int startup_db_reset(clicon_handle h, char *db);
int startup_mode_startup(clicon_handle h, cbuf *cbret);
int startup_extraxml(clicon_handle h, char *file, cbuf *cbret);
int startup_failsafe(clicon_handle h);
int startup_module_state(clicon_handle h, yang_spec *yspec);
#endif /* _BACKEND_STARTUP_H_ */