Merge branch 'modules-state'

This commit is contained in:
Olof hagsand 2019-02-26 16:53:59 +01:00
commit 2394c6f46e
43 changed files with 1755 additions and 587 deletions

View file

@ -3,10 +3,28 @@
## 3.10.0 (Upcoming)
### Major New features
* New backend startup and upgrade support, see [doc/startup.md] for details
* Enable with CLICON_XMLDB_MODSTATE config option
* Tag all datastores with system modules-state as in RFC7895
* Check modules-state tags when loading a datastore at startup
* Check which modules match, and which do not.
* Loading of a "startup" XML or JSON configuration
* Loading of "extra" XML.
* Detection of in-compatible XML and Yang models in the startup configuration.
* An upgrade callback when in-compatible XML is encountered (`ca_upgrade`)
* A "failsafe" mode allowing a user to repair the startup on errors or failed validation.
* Major rewrite of `backend_main.c` and a new module `backend_startup.c`
* Datastore files contain RFC7895 module-state information
* Added modules-state parameter to xmldb_get datastore function
* Set config option `CLICON_XMLDB_MODSTATE` to true
* Enable this if you wish to use the upgrade feature in the new startup functionality.
* Note that this adds bytes to your configs
### API changes on existing features (you may need to change your code)
* Removed obsolete `CLICON_CLI_MODEL_TREENAME_PATCH`
### Minor changes
* Added specific clixon_suberrno code: XMLPARSE_ERRNO to identify XML parse errors.
### Corrected Bugs

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, xpath, xret)) != 0)
if ((retval = yang_modules_state_get(h, yspec, xpath, 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:"
@ -135,84 +136,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))
@ -305,256 +228,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;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
switch (ret){
case -1:
if (clicon_suberrno != XMLPARSE_ERRNO)
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;
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
@ -586,6 +341,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);
@ -796,7 +554,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:"
@ -815,7 +573,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)
@ -876,10 +634,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
@ -887,27 +649,59 @@ 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)
/* 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;
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;
}
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_INVALID, cbret contains info */
}
/* Merge extra XML from file and reset function to running
*/
if (status == STARTUP_OK && startup_mode != SM_NONE){
if ((ret = startup_extraxml(h, extraxml_file, cbret)) < 0)
goto done;
if (ret2status(ret, &status) < 0)
goto done;
/* if status = STARTUP_INVALID, cbret contains info */
}
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;
if (status == STARTUP_INVALID && cbuf_len(cbret))
clicon_log(LOG_NOTICE, "%s: %u %s", __PROGRAM__, getpid(), cbuf_get(cbret));
/* Call backend plugin_start with user -- options */
if (plugin_start_useroptions(h, argv0, argc, argv) <0)
goto done;
@ -952,6 +746,8 @@ main(int argc,
goto done;
retval = 0;
done:
if (cbret)
cbuf_free(cbret);
clicon_log(LOG_NOTICE, "%s: %u Terminated retval:%d", __PROGRAM__, getpid(), retval);
backend_terminate(h); /* Cannot use h after this */

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, NULL, 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_ */

View file

@ -483,7 +483,7 @@ main(int argc, char **argv)
free(restarg);
// Gets in your face if we log on stderr
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
if (h)
cli_terminate(h);
return retval;

View file

@ -531,6 +531,6 @@ main(int argc,
done:
netconf_terminate(h);
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
return 0;
}

View file

@ -90,6 +90,7 @@ struct text_handle {
int th_pretty; /* Store xml/json pretty-printed. */
char *th_nacm_mode;
cxobj *th_nacm_xtree;
cxobj *th_modst; /* According to RFC7895 for rev mgmnt */
};
/* Struct per database in hash */
@ -211,6 +212,8 @@ text_disconnect(xmldb_handle xh)
}
if (th->th_nacm_mode)
free(th->th_nacm_mode);
if (th->th_modst)
xml_free(th->th_modst);
free(th);
}
retval = 0;
@ -247,6 +250,8 @@ text_getopt(xmldb_handle xh,
*value = &th->th_nacm_mode;
else if (strcmp(optname, "nacm_xtree") == 0)
*value = th->th_nacm_xtree;
else if (strcmp(optname, "modules_state") == 0)
*value = th->th_modst;
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@ -305,6 +310,9 @@ text_setopt(xmldb_handle xh,
else if (strcmp(optname, "nacm_xtree") == 0){
th->th_nacm_xtree = (cxobj*)value;
}
else if (strcmp(optname, "modules_state") == 0){
th->th_modst = (cxobj*)value;
}
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@ -434,6 +442,143 @@ xml_copy_marked(cxobj *x0,
return retval;
}
/*! Common read function that reads an XML tree from file
* @param[in] db Symbolic database name, eg "candidate", "running"
* @param[out] xp XML tree read from file
* @param[out] xmsp If set, return modules-state differences
*/
static int
text_readfile(struct text_handle *th,
const char *db,
yang_spec *yspec,
cxobj **xp,
cxobj **xmsp)
{
int retval = -1;
cxobj *x0 = NULL;
char *dbfile = NULL;
int fd = -1;
cxobj *xmsd = NULL; /* Local modules-state diff tree */
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
if ((fd = open(dbfile, O_RDONLY)) < 0) {
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
/* Parse file into XML tree */
if (strcmp(th->th_format,"json")==0){
if ((json_parse_file(fd, yspec, &x0)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */
if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0)
goto done;
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
/* There should only be one element and called config */
if (singleconfigroot(x0, &x0) < 0)
goto done;
}
{
cxobj *xmodst;
cxobj *xm = NULL;
cxobj *xm2;
cxobj *xs;
char *name; /* module name */
char *mrev; /* file revision */
char *srev; /* system revision */
/* Read existing if any and compare with systems module revisions:
* This can happen:
* 1) There is no modules-state info in the file
* 2) There is module state info in the file
* 3) For each module state m in the file:
* 3a) There is no such module in the system
* 3b) File module-state matches system
* 3c) File module-state does not match system
*/
if ((xmodst = xml_find_type(x0, NULL, "modules-state", CX_ELMNT)) == NULL){
/* 1) There is no modules-state info in the file */
}
else{
/* Create a diff tree */
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &xmsd) < 0)
goto done;
if (xml_rootchild(xmsd, 0, &xmsd) < 0)
goto done;
/* 3) For each module state m in the file */
while ((xm = xml_child_each(xmodst, xm, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xm), "module"))
continue;
if ((name = xml_find_body(xm, "name")) == NULL)
continue;
/* 3a) There is no such module in the system */
if ((xs = xpath_first(th->th_modst, "module[name=\"%s\"]", name)) == NULL){
// fprintf(stderr, "%s: Module %s: not in system\n", __FUNCTION__, name);
if ((xm2 = xml_dup(xm)) == NULL)
goto done;
if (xml_addsub(xmsd, xm2) < 0)
goto done;
continue;
}
/* These two shouldnt happen since revision is key, just ignore */
if ((mrev = xml_find_body(xm, "revision")) == NULL)
continue;
if ((srev = xml_find_body(xs, "revision")) == NULL)
continue;
if (strcmp(mrev, srev)==0){
/* 3b) File module-state matches system */
// fprintf(stderr, "%s: Module %s: file \"%s\" and system revisions match\n", __FUNCTION__, name, mrev);
}
else{
/* 3c) File module-state does not match system */
// fprintf(stderr, "%s: Module %s: file \"%s\" and system \"%s\" revisions do not match\n", __FUNCTION__, name, mrev, srev);
if ((xm2 = xml_dup(xm)) == NULL)
goto done;
if (xml_addsub(xmsd, xm2) < 0)
goto done;
}
}
}
if (xmodst){
/* For now ignore the modules_state - just remove it*/
if (xml_purge(xmodst) < 0)
goto done;
}
}
if (xp){
*xp = x0;
x0 = NULL;
}
if (xmsp){
*xmsp = xmsd;
xmsd = NULL;
}
retval = 0;
done:
if (xmsd)
xml_free(xmsd);
if (fd != -1)
close(fd);
if (dbfile)
free(dbfile);
if (x0)
xml_free(x0);
return retval;
}
/*! Get content of database using xpath. return a set of matching sub-trees
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
@ -443,6 +588,7 @@ xml_copy_marked(cxobj *x0,
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] config If set only configuration data, else also state
* @param[out] xret Single return XML tree. Free with xml_free()
* @param[out] xms If set, return modules-state differences
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get the generic API function
@ -452,14 +598,13 @@ text_get(xmldb_handle xh,
const char *db,
char *xpath,
int config,
cxobj **xtop)
cxobj **xtop,
cxobj **xms)
{
int retval = -1;
char *dbfile = NULL;
yang_spec *yspec;
cxobj *xt = NULL;
cxobj *x;
int fd = -1;
cxobj **xvec = NULL;
size_t xlen;
int i;
@ -474,37 +619,10 @@ text_get(xmldb_handle xh,
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
xt = de->de_xml;
}
/* If there is no xml x0 tree (in cache), then read it from file */
if (xt == NULL){
if (text_db2file(th, db, &dbfile) < 0)
if (text_readfile(th, db, yspec, &xt, xms) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
if ((fd = open(dbfile, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
/* Parse file into XML tree */
if (strcmp(th->th_format,"json")==0){
if ((json_parse_file(fd, yspec, &xt)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &xt)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */
if (xml_child_nr(xt) == 0){
if (xml_name_set(xt, "config") < 0)
goto done;
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
/* There should only be one element and called config */
if (singleconfigroot(xt, &xt) < 0)
goto done;
}
/* XXX: should we validate file if read from disk?
* Argument against: we may want to have a semantically wrong file and wish
* to edit?
@ -585,12 +703,8 @@ text_get(xmldb_handle xh,
done:
if (xt)
xml_free(xt);
if (dbfile)
free(dbfile);
if (xvec)
free(xvec);
if (fd != -1)
close(fd);
return retval;
}
@ -1080,7 +1194,6 @@ text_put(xmldb_handle xh,
int retval = -1;
struct text_handle *th = handle(xh);
char *dbfile = NULL;
int fd = -1;
FILE *f = NULL;
cbuf *cb = NULL;
yang_spec *yspec;
@ -1088,6 +1201,9 @@ text_put(xmldb_handle xh,
struct db_element *de = NULL;
int ret;
cxobj *xnacm = NULL;
char *mode;
cxobj *xnacm0 = NULL;
cxobj *xmodst = NULL;
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
@ -1106,53 +1222,22 @@ text_put(xmldb_handle xh,
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
x0 = de->de_xml;
}
/* If there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
if (text_readfile(th, db, yspec, &x0, NULL) < 0)
goto done;
}
if ((fd = open(dbfile, O_RDONLY)) < 0) {
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
/* Parse file into XML tree */
if (strcmp(th->th_format,"json")==0){
if ((json_parse_file(fd, yspec, &x0)) < 0)
goto done;
}
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */
if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0)
goto done;
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
/* There should only be one element and called config */
if (singleconfigroot(x0, &x0) < 0)
goto done;
}
}
/* Here x0 looks like: <config>...</config> */
if (strcmp(xml_name(x0),"config")!=0){
clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
xml_name(x0));
goto done;
}
/* Here x0 looks like: <config>...</config> */
#if 0 /* debug */
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
#endif
#if 1
{
char *mode;
cxobj *xnacm0 = NULL;
mode = th->th_nacm_mode;
if (mode){
if (strcmp(mode, "external")==0)
@ -1167,9 +1252,7 @@ text_put(xmldb_handle xh,
goto done;
}
/* Here assume if xnacm is set (actually may be ret==0?) do NACM */
}
#endif
/*
* Modify base tree x with modification x1. This is where the
* new tree is made.
@ -1206,17 +1289,19 @@ text_put(xmldb_handle xh,
hash_add(th->th_dbs, db, &de0, sizeof(de0));
}
}
if (dbfile == NULL){
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL");
goto done;
}
}
if (fd != -1){
close(fd);
fd = -1;
/* Add module revision info before writing to file)
*/
if (th->th_modst){
if ((xmodst = xml_dup(th->th_modst)) == NULL)
goto done;
if (xml_addsub(x0, xmodst) < 0)
goto done;
}
if ((f = fopen(dbfile, "w")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
@ -1228,14 +1313,16 @@ text_put(xmldb_handle xh,
}
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
goto done;
/* Remove modules state after writing to file
*/
if (xmodst && xml_purge(xmodst) < 0)
goto done;
retval = 1;
done:
if (f != NULL)
fclose(f);
if (dbfile)
free(dbfile);
if (fd != -1)
close(fd);
if (cb)
cbuf_free(cb);
if (!th->th_cache && x0)
@ -1489,7 +1576,7 @@ text_create(xmldb_handle xh,
struct db_element *de = NULL;
cxobj *xt = NULL;
if (th->th_cache){ /* XXX This should nt really happen? */
if (th->th_cache){ /* XXX This should not really happen? */
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL){
if ((xt = de->de_xml) != NULL){
assert(xt==NULL); /* XXX */

View file

@ -39,7 +39,7 @@
/*
* Prototypes
*/
int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop);
int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop, cxobj **xmodst);
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, const char *from, const char *to);

View file

@ -22,7 +22,7 @@ APIs. There are currently plugins for: CLI, Netconf, Restconf, the datastore an
## Which programming language is used?
Clixon is written in C. The plugins are written in C. The CLI
specification uses cligen (http://cligen.se)
specification uses [CLIgen](http://github.com/olofhagsand/cligen)
## How to best understand Clixon?
Run the Clixon example, in the [example](../example) directory.

View file

@ -288,8 +288,9 @@ static clixon_plugin_api api = {
clixon_plugin_init,
plugin_start,
plugin_exit,
.ca_reset=plugin_reset,/* reset */
.ca_reset=plugin_reset,/* reset for extra XML at startup*/
.ca_statedata=plugin_statedata, /* statedata */
.ca_upgrade=example_upgrade, /* upgrade configuration */
.ca_trans_begin=NULL, /* trans begin */
.ca_trans_validate=transaction_validate,/* trans validate */
.ca_trans_complete=NULL, /* trans complete */

View file

@ -39,6 +39,7 @@
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <syslog.h>
#include <sys/time.h>
/* clicon */
@ -50,6 +51,13 @@
/* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h>
/* Variable to control if reset code is run.
* The reset code inserts "extra XML" which assumes ietf-interfaces is
* loaded, and this is not always the case.
* Therefore, the backend must be started with -- -r to enable the reset function
*/
static int _reset = 0;
/* forward */
static int example_stream_timer_setup(clicon_handle h);
@ -212,6 +220,25 @@ example_statedata(clicon_handle h,
return retval;
}
/*! Upgrade configuration from one version to another
* @param[in] h Clicon handle
* @param[in] xms Module state differences
* @retval 0 OK
* @retval -1 Error
*/
int
example_upgrade(clicon_handle h,
cxobj *xms)
{
int retval = -1;
if (xms)
clicon_log_xml(LOG_NOTICE, xms, "%s", __FUNCTION__);
retval = 0;
// done:
return retval;
}
/*! Plugin state reset. Add xml or set state in backend machine.
* Called in each backend plugin. plugin_reset is called after all plugins
* have been initialized. This give the application a chance to reset
@ -233,6 +260,7 @@ example_reset(clicon_handle h,
int ret;
cbuf *cbret = NULL;
goto ok; /* Note not enabled by default */
if (xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface>"
"<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0)
@ -252,6 +280,7 @@ example_reset(clicon_handle h,
cbuf_get(cbret));
goto done;
}
ok:
retval = 0;
done:
if (cbret)
@ -267,7 +296,8 @@ example_reset(clicon_handle h,
* @param[in] argv Argument vector
*
* plugin_start is called once everything has been initialized, right before
* the main event loop is entered. Command line options can be passed to the
* the main event loop is entered.
* From the CLI, command line options can be passed to the
* plugins by using "-- <args>" where <args> is any choice of
* options specific to the application. These options are passed to the
* plugin_start function via the argc and argv arguments which
@ -278,6 +308,16 @@ example_start(clicon_handle h,
int argc,
char **argv)
{
char c;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "r")) != -1)
switch (c) {
case 'r':
_reset = 1;
break;
}
return 0;
}
@ -296,6 +336,7 @@ static clixon_plugin_api api = {
example_exit, /* exit */
.ca_reset=example_reset, /* reset */
.ca_statedata=example_statedata, /* statedata */
.ca_upgrade=example_upgrade, /* upgrade configuration */
.ca_trans_begin=NULL, /* trans begin */
.ca_trans_validate=transaction_validate,/* trans validate */
.ca_trans_complete=NULL, /* trans complete */

View file

@ -120,7 +120,7 @@ clixon_plugin_init(clicon_handle h)
clicon_debug(1, "%s backend nacm", __FUNCTION__);
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
clicon_log(LOG_WARNING, "%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;
}
return &api;

View file

@ -50,4 +50,3 @@ int strverscmp (__const char *__s1, __const char *__s2);
/* Full xmlns validation check is made only if XML has associated YANG spec
*/
#define XMLNS_YANG_ONLY 1

View file

@ -46,6 +46,11 @@
*/
#define ERR_STRLEN 256
/* Special error number for clicon_suberrno
* For catching xml parse errors as exceptions
*/
#define XMLPARSE_ERRNO 898943
/*
* Types
* Add error here, but must also add an entry in EV variable.

View file

@ -198,6 +198,10 @@ int clicon_xmldb_handle_set(clicon_handle h, void *xh);
char *clicon_username_get(clicon_handle h);
int clicon_username_set(clicon_handle h, void *username);
/* Set and get startup status */
enum startup_status clicon_startup_status_get(clicon_handle h);
int clicon_startup_status_set(clicon_handle h, enum startup_status status);
/* Set and get socket fd (ie backend server socket / restconf fcgx socket */
int clicon_socket_get(clicon_handle h);
int clicon_socket_set(clicon_handle h, int s);

View file

@ -107,7 +107,7 @@ typedef void *transaction_data;
/* Transaction callbacks */
typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
/* Hook to override default prompt with explicit function
/*! Hook to override default prompt with explicit function
* Format prompt before each getline
* @param[in] h Clicon handle
* @param[in] mode Cligen syntax mode
@ -115,6 +115,27 @@ typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
*/
typedef char *(cli_prompthook_t)(clicon_handle, char *mode);
/*! Startup status for use in startup-callback
* Note that for STARTUP_ERR and _INVALID, running runs in failsafe mode
* and startup contains the erroneous or invalid database.
* The user should repair the startup and
* (1) restart he backend
* (2) copy startup to candidate and commit.
*/
enum startup_status{
STARTUP_ERR, /* XML/JSON syntax error */
STARTUP_INVALID, /* XML/JSON OK, but (yang) validation fails */
STARTUP_OK /* Everything OK (may still be modules-mismatch) */
};
/*! Upgrade configuration callback given a diff of yang module state
* @param[in] h Clicon handle
* @param[in] xms XML tree of module state differences
* @retval 0 OK
* @retval -1 Error
*/
typedef int (upgrade_cb_t)(clicon_handle, cxobj *xms);
/* plugin init struct for the api
* Note: Implicit init function
*/
@ -139,14 +160,16 @@ struct clixon_plugin_api{
struct {
} cau_netconf;
struct {
plgreset_t *cb_reset; /* Reset system status (backend only) */
plgreset_t *cb_reset; /* Reset system status */
plgstatedata_t *cb_statedata; /* Get state data from plugin (backend only) */
upgrade_cb_t *cb_upgrade; /* Upgrade callback */
trans_cb_t *cb_trans_begin; /* Transaction start */
trans_cb_t *cb_trans_validate; /* Transaction validation */
trans_cb_t *cb_trans_complete; /* Transaction validation complete */
trans_cb_t *cb_trans_commit; /* Transaction commit */
trans_cb_t *cb_trans_end; /* Transaction completed */
trans_cb_t *cb_trans_abort; /* Transaction aborted */
} cau_backend;
} u;
@ -158,6 +181,7 @@ struct clixon_plugin_api{
#define ca_auth u.cau_restconf.cr_auth
#define ca_reset u.cau_backend.cb_reset
#define ca_statedata u.cau_backend.cb_statedata
#define ca_upgrade u.cau_backend.cb_upgrade
#define ca_trans_begin u.cau_backend.cb_trans_begin
#define ca_trans_validate u.cau_backend.cb_trans_validate
#define ca_trans_complete u.cau_backend.cb_trans_complete

View file

@ -75,7 +75,7 @@ typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value);
typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
/* Type of xmldb get function */
typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop);
typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop, cxobj **xmodst);
/* Type of xmldb put function */
typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
@ -138,7 +138,7 @@ int xmldb_connect(clicon_handle h);
int xmldb_disconnect(clicon_handle h);
int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop);
int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop, cxobj **xmodst);
int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
int xmldb_copy(clicon_handle h, const char *from, const char *to);
int xmldb_lock(clicon_handle h, const char *db, int pid);

View file

@ -53,7 +53,8 @@
int modules_state_cache_set(clicon_handle h, cxobj *msx);
int yang_modules_init(clicon_handle h);
char *yang_modules_revision(clicon_handle h);
int yang_modules_state_get(clicon_handle h, yang_spec *yspec, char *xpath,
cxobj **xret);
int brief, cxobj **xret);
#endif /* _CLIXON_YANG_MODULE_H_ */

View file

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

View file

@ -889,6 +889,36 @@ clicon_username_set(clicon_handle h,
return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0;
}
/*! Get backend daemon startup status
* @param[in] h Clicon handle
* @retval status Startup status
*/
enum startup_status
clicon_startup_status_get(clicon_handle h)
{
clicon_hash_t *cdat = clicon_data(h);
void *p;
if ((p = hash_value(cdat, "startup_status", NULL)) != NULL)
return *(enum startup_status *)p;
return STARTUP_ERR;
}
/*! Set backend daemon startup status
* @param[in] h Clicon handle
* @param[in] status Startup status
* @retval 0 OK
* @retval -1 Error (when setting value)
*/
int
clicon_startup_status_set(clicon_handle h,
enum startup_status status)
{
clicon_hash_t *cdat = clicon_data(h);
if (hash_add(cdat, "startup_status", &status, sizeof(status))==NULL)
return -1;
return 0;
}
/*! Get socket fd (ie backend server socket / restconf fcgx socket)
* @param[in] h Clicon handle

View file

@ -210,7 +210,7 @@ plugin_load_one(clicon_handle h,
clicon_err_reset();
if ((api = initfn(h)) == NULL) {
if (!clicon_errno){ /* if clicon_err() is not called then log and continue */
clicon_log(LOG_WARNING, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
clicon_log(LOG_DEBUG, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
dlclose(handle);
}
else{

View file

@ -133,10 +133,13 @@ struct xml{
/*
* Variables
*/
/* Iterate through modules to find the matching datanode
/* If set to 1 which is default, strict namespace checking of XML is made.
* If set to 0, "loose" namespace semantics is applied.
* This means: iterate through all yang modules to find matching datanode
* or rpc if no xmlns attribute specifies namespace.
* This is loose semantics of finding namespaces.
* And it is wrong, but is the way Clixon originally was written."
* This is _wrong_, but is the way Clixon originally was written, and some
* code still relies on it.
* This, of course, should change.
* @see CLICON_XML_NS_STRICT clixon configure option
*/
int _CLICON_XML_NS_STRICT = 1;
@ -1129,6 +1132,7 @@ xml_find_type_value(cxobj *xt,
* @code
* cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR);
* @endcode
* @see xml_find which finds any child given name
* @see xml_find_value where a body can be found as well
*/
cxobj *
@ -1708,7 +1712,7 @@ xml_parse_file(int fd,
* cxobj *xt = NULL;
* if (xml_parse_string(str, yspec, &xt) < 0)
* err;
* if (xml_root_child(xt, 0, &xt) < 0) # If you want to remove TOP
* if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
* err;
* @endcode
* @see xml_parse_file
@ -1778,7 +1782,11 @@ xml_parse_va(cxobj **xtop,
return retval;
}
/*! Copy single xml node frm x0 to x1 without copying children
/*! Copy single xml node from x0 to x1 without copying children
* @param[in] x0 Source XML tree
* @param[in] x1 Destination XML tree (must exist)
* @retval 0 OK
* @retval -1 Error
*/
int
xml_copy_one(cxobj *x0,
@ -1806,6 +1814,10 @@ xml_copy_one(cxobj *x0,
*
* x1 should be a created placeholder. If x1 is non-empty,
* the copied tree is appended to the existing tree.
* @param[in] x0 Source XML tree
* @param[in] x1 Destination XML tree (must exist)
* @retval 0 OK
* @retval -1 Error
* @code
* x1 = xml_new("new", xparent, NULL);
* if (xml_copy(x0, x1) < 0)

View file

@ -325,11 +325,12 @@ xmldb_setopt(clicon_handle h,
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] config If set only configuration data, else also state
* @param[out] xret Single return XML tree. Free with xml_free()
* @param[out] xms If set, return modules-state differences
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt) < 0)
* if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt, NULL) < 0)
* err;
* xml_free(xt);
* @endcode
@ -341,7 +342,8 @@ xmldb_get(clicon_handle h,
const char *db,
char *xpath,
int config,
cxobj **xret)
cxobj **xret,
cxobj **xms)
{
int retval = -1;
xmldb_handle xh;
@ -359,7 +361,7 @@ xmldb_get(clicon_handle h,
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
goto done;
}
retval = xa->xa_get_fn(xh, db, xpath, config, xret);
retval = xa->xa_get_fn(xh, db, xpath, config, xret, xms);
#if DEBUG
if (retval == 0) {
cbuf *cb = cbuf_new();

View file

@ -81,7 +81,7 @@
void
clixon_xml_parseerror(void *_ya, char *s)
{
clicon_err(OE_XML, 0, "xml_parse: line %d: %s: at or before: %s",
clicon_err(OE_XML, XMLPARSE_ERRNO, "xml_parse: line %d: %s: at or before: %s",
_YA->ya_linenum, s, clixon_xml_parsetext);
return;
}
@ -117,7 +117,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya,
char *ver)
{
if(strcmp(ver, "1.0")){
clicon_err(OE_XML, errno, "Wrong XML version %s expected 1.0", ver);
clicon_err(OE_XML, XMLPARSE_ERRNO, "Wrong XML version %s expected 1.0", ver);
free(ver);
return -1;
}
@ -224,12 +224,12 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
cxobj *xc;
if (strcmp(xml_name(x), name)){
clicon_err(OE_XML, 0, "XML parse sanity check failed: %s vs %s",
clicon_err(OE_XML, XMLPARSE_ERRNO, "XML parse sanity check failed: %s vs %s",
xml_name(x), name);
goto done;
}
if (xml_prefix(x)!=NULL){
clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s",
clicon_err(OE_XML, XMLPARSE_ERRNO, "XML parse sanity check failed: %s:%s vs %s",
xml_prefix(x), xml_name(x), name);
goto done;
}
@ -267,7 +267,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
cxobj *xc;
if (strcmp(xml_name(x), name)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s",
clicon_err(OE_XML, XMLPARSE_ERRNO, "Sanity check failed: %s:%s vs %s:%s",
xml_prefix(x),
xml_name(x),
namespace,
@ -276,7 +276,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
}
if (xml_prefix(x)==NULL ||
strcmp(xml_prefix(x), namespace)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s",
clicon_err(OE_XML, XMLPARSE_ERRNO, "Sanity check failed: %s:%s vs %s:%s",
xml_prefix(x),
xml_name(x),
namespace,

View file

@ -189,6 +189,7 @@ modules_state_cache_set(clicon_handle h,
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in] xpath XML Xpath
* @param[in] brief Just name,revision and uri (no cache)
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
@ -212,10 +213,149 @@ x +--ro namespace inet:uri
+--ro schema? inet:uri
* @see netconf_create_hello
*/
#if 1
/*! Actually build the yang modules state XML tree
*/
static int
yms_build(clicon_handle h,
yang_spec *yspec,
char *msid,
int brief,
cbuf *cb)
{
int retval = -1;
yang_stmt *ylib = NULL; /* ietf-yang-library */
char *module = "ietf-yang-library";
yang_stmt *ys;
yang_stmt *yc;
yang_stmt *ymod; /* generic module */
yang_stmt *yns = NULL; /* namespace */
if ((ylib = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL &&
(ylib = yang_find((yang_node*)yspec, Y_SUBMODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s not found", module);
goto done;
}
if ((yns = yang_find((yang_node*)ylib, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
goto done;
}
cprintf(cb,"<modules-state xmlns=\"%s\">", yns->ys_argument);
cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (ymod->ys_keyword != Y_MODULE &&
ymod->ys_keyword != Y_SUBMODULE)
continue;
cprintf(cb,"<module>");
cprintf(cb,"<name>%s</name>", ymod->ys_argument);
if ((ys = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
if ((ys = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) != NULL)
cprintf(cb,"<namespace>%s</namespace>", ys->ys_argument);
else
cprintf(cb,"<namespace></namespace>");
/* This follows order in rfc 7895: feature, conformance-type,
submodules */
if (!brief){
yc = NULL;
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
switch(yc->ys_keyword){
case Y_FEATURE:
if (yc->ys_cv && cv_bool_get(yc->ys_cv))
cprintf(cb,"<feature>%s</feature>", yc->ys_argument);
break;
default:
break;
}
}
cprintf(cb, "<conformance-type>implement</conformance-type>");
}
yc = NULL;
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
switch(yc->ys_keyword){
case Y_SUBMODULE:
cprintf(cb,"<submodule>");
cprintf(cb,"<name>%s</name>", yc->ys_argument);
if ((ys = yang_find((yang_node*)yc, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
cprintf(cb,"</submodule>");
break;
default:
break;
}
}
cprintf(cb,"</module>");
}
cprintf(cb,"</modules-state>");
retval = 0;
done:
return retval;
}
int
yang_modules_state_get(clicon_handle h,
yang_spec *yspec,
char *xpath,
int brief,
cxobj **xret)
{
int retval = -1;
cxobj *x = NULL;
char *msid; /* modules-set-id */
cxobj *x1;
cbuf *cb = NULL;
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if (!brief && modules_state_cache_get(h, msid, &x) < 0)
goto done;
if (x != NULL){ /* Yes a cache (but no duplicate) */
if (xpath_first(x, "%s", xpath)){
if ((x1 = xml_dup(x)) == NULL)
goto done;
x = x1;
}
else
x = NULL;
}
else { /* No cache -> build the tree */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "clicon buffer");
goto done;
}
if (yms_build(h, yspec, msid, brief, cb) < 0)
goto done;
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
retval = 1;
goto done;
}
if (!brief && modules_state_cache_set(h, x) < 0) /* move to fn above? */
goto done;
}
if (x && netconf_trymerge(x, yspec, xret) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (x)
xml_free(x);
return retval;
}
#else
int
yang_modules_state_get(clicon_handle h,
yang_spec *yspec,
char *xpath,
int brief,
cxobj **xret)
{
int retval = -1;
@ -259,6 +399,7 @@ yang_modules_state_get(clicon_handle h,
cprintf(cb,"<modules-state xmlns=\"%s\">", yns->ys_argument);
cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
<<<<<<< HEAD
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (ymod->ys_keyword != Y_MODULE &&
@ -270,12 +411,33 @@ yang_modules_state_get(clicon_handle h,
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
=======
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (ymod->ys_keyword != Y_MODULE &&
ymod->ys_keyword != Y_SUBMODULE)
continue;
cprintf(cb,"<module>");
cprintf(cb,"<name>%s</name>", ymod->ys_argument);
if ((ys = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
if (!brief){
>>>>>>> modules-state
if ((ys = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) != NULL)
cprintf(cb,"<namespace>%s</namespace>", ys->ys_argument);
else
cprintf(cb,"<namespace></namespace>");
<<<<<<< HEAD
/* This follows order in rfc 7895: feature, conformance-type, submodules */
yc = NULL;
=======
}
/* This follows order in rfc 7895: feature, conformance-type, submodules */
yc = NULL;
if (!brief)
>>>>>>> modules-state
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
switch(yc->ys_keyword){
case Y_FEATURE:
@ -286,6 +448,7 @@ yang_modules_state_get(clicon_handle h,
break;
}
}
<<<<<<< HEAD
cprintf(cb, "<conformance-type>implement</conformance-type>");
yc = NULL;
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
@ -302,6 +465,25 @@ yang_modules_state_get(clicon_handle h,
default:
break;
}
=======
if (!brief)
cprintf(cb, "<conformance-type>implement</conformance-type>");
yc = NULL;
if (!brief)
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
switch(yc->ys_keyword){
case Y_SUBMODULE:
cprintf(cb,"<submodule>");
cprintf(cb,"<name>%s</name>", yc->ys_argument);
if ((ys = yang_find((yang_node*)yc, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
cprintf(cb,"</submodule>");
break;
default:
break;
>>>>>>> modules-state
}
cprintf(cb,"</module>");
}
@ -326,3 +508,4 @@ yang_modules_state_get(clicon_handle h,
cbuf_free(cb);
return retval;
}
#endif

View file

@ -10,6 +10,7 @@ fi
err=0
testnr=0
for test in test*.sh; do
if [ $testnr != 0 ]; then echo; fi
testfile=$test
. ./$test
errcode=$?

View file

@ -59,6 +59,10 @@ testname=
# If set to 0, override starting of clixon_backend in test (you bring your own)
: ${BE:=1}
# If BE is set, some tests have a user timeout to show which params to set
# for starting a backend
: ${BETIMEOUT:=10}
# If set, enable debugging (of backend)
: ${DBG:=0}
@ -95,7 +99,12 @@ dir=/var/tmp/$0
if [ ! -d $dir ]; then
mkdir $dir
fi
# If we bring our own backend BE=0 (it is already started),the backend may
# have created some files (eg unix socket) in $dir and therefore cannot
# be deleted
if [ $BE -ne 0 ]; then
rm -rf $dir/*
fi
# error and exit,
# arg1: expected

View file

@ -1,6 +1,5 @@
#!/bin/bash
# Test of backward compatibility
# 1) Load <3.9 startup/running/extra files without namespaces - ensure it returns namespaces
# Test of backward compatibility, from last release to newer.
#
# Magic line must be first in script (see README.md)
@ -35,82 +34,6 @@ cat <<EOF > $cfg
EOF
testrun(){
mode=$1
expect=$2
dbdir=$dir/db
cat <<EOF > $dbdir
<config>
<interfaces>
<interface>
<name>run</name>
<type>ex:eth</type>
</interface>
</interfaces>
</config>
EOF
sudo mv $dbdir /usr/local/var/$APPNAME/running_db
cat <<EOF > $dbdir
<config>
<interfaces>
<interface>
<name>startup</name>
<type>ex:eth</type>
</interface>
</interfaces>
</config>
EOF
sudo mv $dbdir /usr/local/var/$APPNAME/startup_db
cat <<EOF > $dir/config
<config>
<interfaces>
<interface>
<name>extra</name>
<type>ex:eth</type>
</interface>
</interfaces>
</config>
EOF
new "test params: -f $cfg -s $mode -c $dir/config"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -f $cfg -s $mode -c $dir/config"
start_backend -f $cfg -s $mode -c $dir/config
new "waiting"
sleep $RCWAIT
fi
new "Check $mode"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' "^<rpc-reply>$expect</rpc-reply>]]>]]>$"
if [ $BE -eq 0 ]; then
return # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
} # testrun
testrun running '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>lo</name><type>ex:loopback</type><enabled>true</enabled></interface><interface><name>run</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun startup '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>lo</name><type>ex:loopback</type><enabled>true</enabled></interface><interface><name>startup</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
# Nothing
rm -rf $dir

178
test/test_copy_config.sh Executable file
View file

@ -0,0 +1,178 @@
#!/bin/bash
# RFC 6241:
# 7.3 - Even if it advertises the :writable-running capability, a device
# MAY choose not to support the <running/> configuration datastore
# as the <target> parameter of a <copy-config> operation.
# - If the <source> and <target> parameters identify the same URL or
# configuration datastore, an error MUST be returned with an error-
# tag containing "invalid-value".
# 8.3.5.1 The candidate configuration can be used as a source or target
# 8.4.1 If :startup capability is advertized, <copy-config> from running to
# startup is also necessary
# 8.8.5.2 :url capability <copy-config> accepts <url> element as value of
# the <source> and the <target> parameters.
#
# The test checks that these are allowed:
# running->startup
# running->candidate
# candidate->startup
# startup->candidate
# And checks that copying to running is not allowed
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
# Use yang in example
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_MODULE_SET_ID>42</CLICON_MODULE_SET_ID>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_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_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>$dir/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
</config>
EOF
# Create empty startup
cat <<EOF > $dir/startup_db
<config/>
EOF
# rm candidate and running
rm -f $dir/running_db
rm -f $dir/candidate_db
new "test params: -f $cfg"
# Bring your own backend
if [ $BE -ne 0 ]; then
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
# start new backend
echo "sudo $clixon_backend -s init -f $cfg -D $DBG"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "Add config to candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="create"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit to running"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="delete"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
# Here startup and candidate are empty, only running has content
# test running->startup and running->candidate
new "Check candidate empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "Check startup empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><startup/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "copy running->startup"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><startup/></target><source><running/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "copy running->candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><running/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Here startup and candidate have content
new "Check candidate content"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
new "Check startup content"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><startup/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
new "Delete startup"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Here startup is empty and candidate has content
# test candidate->startup
new "Check startup empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><startup/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "copy candidate->startup"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><startup/></target><source><candidate/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check startup content"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><startup/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
new "Delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="delete"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
# Here candidate is empty and startup has content
# test startup->candidate
new "Check candidate empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "copy startup->candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check candidate content"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth/0/0</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
# Negative test: check copying to running is not allowed
new "Delete candidate"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface operation="delete"><name>eth/0/0</name><type>ex:eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "netconf commit to running"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Here running is empty
new "Check running empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><running/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
# Add to candidate
new "copy startup->candidate"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "copy startup->running not allowed"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><running/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>running</bad-element></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$"
# Here running is empty
new "Check running empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc message-id="101"><get-config><source><running/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
if [ $BE -eq 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

View file

@ -155,7 +155,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '<rpc-reply><data
# Check as file
new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted."
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><c xmlns="urn:example:order"><d>hej</d></c><l xmlns="urn:example:order">hopp</l><y0 xmlns="urn:example:order">d</y0><y0 xmlns="urn:example:order">b</y0><y0 xmlns="urn:example:order">c</y0><y0 xmlns="urn:example:order">a</y0><y1 xmlns="urn:example:order">a</y1><y1 xmlns="urn:example:order">b</y1><y1 xmlns="urn:example:order">c</y1><y1 xmlns="urn:example:order">d</y1><y2 xmlns="urn:example:order"><k>d</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y3 xmlns="urn:example:order"><k>a</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>b</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>c</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>d</k><a>bar</a></y3><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>lo</name><type>ex:loopback</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><c xmlns="urn:example:order"><d>hej</d></c><l xmlns="urn:example:order">hopp</l><y0 xmlns="urn:example:order">d</y0><y0 xmlns="urn:example:order">b</y0><y0 xmlns="urn:example:order">c</y0><y0 xmlns="urn:example:order">a</y0><y1 xmlns="urn:example:order">a</y1><y1 xmlns="urn:example:order">b</y1><y1 xmlns="urn:example:order">c</y1><y1 xmlns="urn:example:order">d</y1><y2 xmlns="urn:example:order"><k>d</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y3 xmlns="urn:example:order"><k>a</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>b</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>c</k><a>bar</a></y3><y3 xmlns="urn:example:order"><k>d</k><a>bar</a></y3></data></rpc-reply>]]>]]>$'
new "get each ordered-by user leaf-list"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/y2[k='a']\"/></get-config></rpc>]]>]]>" '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>a</k><a>bar</a></y2></data></rpc-reply>]]>]]>$'

View file

@ -1,6 +1,7 @@
#!/bin/bash
# Startup test: Start clicon daemon in the (four) different startup modes
# and the dbs and files are setup as follows:
# (init, none, running, or startup)
# The dbs and files are setup as follows:
# - The example reset_state callback adds "lo" interface
# - An extra xml configuration file starts with an "extra" interface
# - running db starts with a "run" interface
@ -27,8 +28,7 @@ cat <<EOF > $cfg
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
@ -36,12 +36,17 @@ cat <<EOF > $cfg
EOF
# Create a pre-set running, startup and (extra) config.
# The configs are identified by an interface called run, startup, extra.
# Depending on startup mode (init, none, running, or startup)
# expect different output of an initial get-config
testrun(){
mode=$1
expect=$2
dbdir=$dir/db
cat <<EOF > $dbdir
# Create running-db containin the interface "run"
sudo rm -f $dir/running_db
cat <<EOF > $dir/running_db
<config>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
@ -51,9 +56,10 @@ testrun(){
</interfaces>
</config>
EOF
sudo mv $dbdir /usr/local/var/$APPNAME/running_db
cat <<EOF > $dbdir
# Create startup-db containin the interface "startup"
sudo rm -f $dir/startup_db
cat <<EOF > $dir/startup_db
<config>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
@ -63,8 +69,9 @@ EOF
</interfaces>
</config>
EOF
sudo mv $dbdir /usr/local/var/$APPNAME/startup_db
# Create extra xml containin the interface "extra"
sudo rm -f $dir/config
cat <<EOF > $dir/config
<config>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
@ -76,20 +83,23 @@ EOF
</config>
EOF
if [ $BE -ne 0 ]; then # Bring your own backend
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -f $cfg -s $mode -c $dir/config"
start_backend -s $mode -f $cfg -c $dir/config
new "waiting"
sleep $RCWAIT
new "Check $mode"
else
new "Restart backend as eg follows: -Ff $cfg -s $mode -c $dir/config # $BETIMEOUT s"
sleep $BETIMEOUT
fi
new "Startup test for init mode: $mode"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' "^<rpc-reply>$expect</rpc-reply>]]>]]>$"
new "Kill backend"
@ -102,12 +112,12 @@ EOF
stop_backend -f $cfg
} # testrun
testrun init '<data/>'
testrun init '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun none '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>run</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun running '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>lo</name><type>ex:loopback</type><enabled>true</enabled></interface><interface><name>run</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun running '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>run</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun startup '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>lo</name><type>ex:loopback</type><enabled>true</enabled></interface><interface><name>startup</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
testrun startup '<data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>extra</name><type>ex:eth</type><enabled>true</enabled></interface><interface><name>startup</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data>'
rm -rf $dir

333
test/test_upgrade.sh Executable file
View file

@ -0,0 +1,333 @@
#!/bin/bash
# Starting clixon with outdated (or not) modules
# This relieas on storing RFC7895 YANG Module Library modules-state info
# in the datastore (or XML files?)
# There is also a: Factory default Setting:
# draft-wu-netconf-restconf-factory-restore-03
# And: A YANG Data Model for module revision management:
# draft-wang-netmod-module-revision-management-01
# The test is made with three Yang models A, B and C as follows:
# Yang module A has revisions "814-01-28" and "2019-01-01"
# Yang module B has only revision "2019-01-01"
# Yang module C has only revision "2019-01-01"
# The system is started YANG modules:
# A revision "2019-01-01"
# B revision "2019-01-01"
# The (startup) configuration XML file has:
# A revision "814-01-28";
# B revision "2019-01-01"
# C revision "2019-01-01"
# Which means the following:
# A has an obsolete version
# containing a0 which has been removed, and a1 which is OK
# B has a compatible version
# C is not present in the system
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
fyangA0=$dir/A@814-01-28.yang
fyangA1=$dir/A@2019-01-01.yang
fyangB=$dir/B@2019-01-01.yang
# Yang module A revision "814-01-28"
# Note that this Yang model will exist in the DIR but will not be loaded
# by the system. Just here for reference
# XXX: Maybe it should be loaded and used in draft-wu?
cat <<EOF > $fyangA0
module A{
prefix a;
revision 814-01-28;
namespace "urn:example:a";
leaf a0{
type string;
}
leaf a1{
type string;
}
}
EOF
# Yang module A revision "2019-01-01"
cat <<EOF > $fyangA1
module A{
prefix a;
revision 2019-01-01;
revision 814-01-28;
namespace "urn:example:a";
/* leaf a0 has been removed */
leaf a1{
description "exists in both versions";
type string;
}
leaf a2{
description "has been added";
type string;
}
}
EOF
# Yang module B revision "2019-01-01"
cat <<EOF > $fyangB
module B{
prefix b;
revision 2019-01-01;
namespace "urn:example:b";
leaf b{
type string;
}
}
EOF
# Yang module C revision "2019-01-01" (note not written to yang dir)
cat <<EOF > /dev/null
module C{
prefix c;
revision 2019-01-01;
namespace "urn:example:c";
leaf c{
type string;
}
}
EOF
# Create configuration
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_XMLDB_MODSTATE>true</CLICON_XMLDB_MODSTATE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
</config>
EOF
# Create failsafe db
cat <<EOF > $dir/failsafe_db
<config>
<a1 xmlns="urn:example:a">always work</a1>
</config>
EOF
# Create compatible startup db
# startup config XML with following
cat <<EOF > $dir/compat-valid.xml
<config>
<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set-id>42</module-set-id>
<module>
<name>A</name>
<revision>2019-01-01</revision>
<namespace>urn:example:a</namespace>
</module>
<module>
<name>B</name>
<revision>2019-01-01</revision>
<namespace>urn:example:b</namespace>
</module>
</modules-state>
<a1 xmlns="urn:example:a">always work</a1>
<b xmlns="urn:example:b">other text</b>
</config>
EOF
# Create compatiblae startup db
# startup config XML with following
cat <<EOF > $dir/compat-invalid.xml
<config>
<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set-id>42</module-set-id>
<module>
<name>A</name>
<revision>2019-01-01</revision>
<namespace>urn:example:a</namespace>
</module>
<module>
<name>B</name>
<revision>2019-01-01</revision>
<namespace>urn:example:b</namespace>
</module>
</modules-state>
<a0 xmlns="urn:example:a">old version</a0>
<a1 xmlns="urn:example:a">always work</a1>
<b xmlns="urn:example:b">other text</b>
<c xmlns="urn:example:c">bla bla</c>
</config>
EOF
# Create non-compat valid startup db
# startup config XML with following (A obsolete, B OK, C lacking)
# But XML is OK
cat <<EOF > $dir/non-compat-valid.xml
<config>
<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set-id>42</module-set-id>
<module>
<name>A</name>
<revision>814-01-28</revision>
<namespace>urn:example:a</namespace>
</module>
<module>
<name>B</name>
<revision>2019-01-01</revision>
<namespace>urn:example:b</namespace>
</module>
<module>
<name>C</name>
<revision>2019-01-01</revision>
<namespace>urn:example:c</namespace>
</module>
</modules-state>
<a1 xmlns="urn:example:a">always work</a1>
<b xmlns="urn:example:b">other text</b>
</config>
EOF
# Create non-compat startup db
# startup config XML with following (A obsolete, B OK, C lacking)
cat <<EOF > $dir/non-compat-invalid.xml
<config>
<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set-id>42</module-set-id>
<module>
<name>A</name>
<revision>814-01-28</revision>
<namespace>urn:example:a</namespace>
</module>
<module>
<name>B</name>
<revision>2019-01-01</revision>
<namespace>urn:example:b</namespace>
</module>
<module>
<name>C</name>
<revision>2019-01-01</revision>
<namespace>urn:example:c</namespace>
</module>
</modules-state>
<a0 xmlns="urn:example:a">old version</a0>
<a1 xmlns="urn:example:a">always work</a1>
<b xmlns="urn:example:b">other text</b>
<c xmlns="urn:example:c">bla bla</c>
</config>
EOF
# Compatible startup with syntax errors
cat <<EOF > $dir/compat-err.xml
<config>
<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set-id>42</module-set-id>
<module>
<name>A</name>
<revision>2019-01-01</revision>
<namespace>urn:example:a</namespace>
</module>
<module>
<name>B</name>
<revision>2019-01-01</revision>
<namespace>urn:example:b</namespace>
</module>
</modules-state>
<<a3 xmlns="urn:example:a">always work</a2>
<b xmlns="urn:example:b">other text
</config>
EOF
# Start system in $mode with existing (old) configuration in $db
# mode is one of: init, none, running, or startup
# db is one of: running_db or startup_db
runtest(){
mode=$1
expect=$2
startup=$3
new "test params: -f $cfg"
# Bring your own backend
if [ $BE -ne 0 ]; then
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s $mode -f $cfg"
# start new backend
sudo $clixon_backend -s $mode -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
else
new "Restart backend as eg follows: -Ff $cfg -s $mode ($BETIMEOUT s)"
sleep $BETIMEOUT
fi
new "Get running"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' "^<rpc-reply>$expect</rpc-reply>]]>]]>$"
new "Get startup"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>' "^<rpc-reply>$startup</rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
fi
}
# Compatible == all yang modules match
# runtest <mode> <expected running> <expected startup>
new "1. Load compatible valid startup (all OK)"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-valid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>'
new "2. Load compatible running valid running (rest of tests are startup)"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-valid.xml running_db)
runtest running '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>'
new "3. Load non-compat valid startup"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-valid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b></data>'
new "4. Load non-compat invalid startup. Enter failsafe, startup invalid."
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-invalid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b><a0 xmlns="urn:example:a">old version</a0><c xmlns="urn:example:c">bla bla</c></data>'
new "5. Load non-compat invalid running. Enter failsafe, startup invalid."
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-invalid.xml running_db)
runtest running '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b><a0 xmlns="urn:example:a">old version</a0><c xmlns="urn:example:c">bla bla</c></data>'
new "6. Load compatible invalid startup."
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-invalid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><a1 xmlns="urn:example:a">always work</a1><b xmlns="urn:example:b">other text</b><a0 xmlns="urn:example:a">old version</a0><c xmlns="urn:example:c">bla bla</c></data>'
new "7. Load non-compat startup. Syntax fail, enter failsafe, startup invalid"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-err.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>read registry</error-message></rpc-error>'
if [ $BE -ne 0 ]; then
rm -rf $dir
fi

View file

@ -202,7 +202,7 @@ main(int argc, char **argv)
xpath = argv[1];
else
xpath = "/";
if (xmldb_get(h, db, xpath, 0, &xt) < 0)
if (xmldb_get(h, db, xpath, 0, &xt, NULL) < 0)
goto done;
clicon_xml2file(stdout, xt, 0, 0);
@ -218,7 +218,7 @@ main(int argc, char **argv)
else
xpath = "/";
for (i=0;i<nr;i++){
if (xmldb_get(h, db, xpath, 0, &xt) < 0)
if (xmldb_get(h, db, xpath, 0, &xt, NULL) < 0)
goto done;
if (xt == NULL){
clicon_err(OE_DB, 0, "xt is NULL");

View file

@ -364,6 +364,14 @@ module clixon-config {
If set, insert spaces and line-feeds making the XML/JSON human
readable. If not set, make the XML/JSON more compact.";
}
leaf CLICON_XMLDB_MODSTATE {
type boolean;
default false;
description
"If set, tag datastores with RFC 7895 YANG Module Library
info. When loaded at startup, a check is made if the system
yang modules match";
}
leaf CLICON_XML_NS_STRICT {
type boolean;
default true;
@ -474,5 +482,6 @@ module clixon-config {
data to store before dropping. 0 means no retention";
}
}
}