Merge branch 'master' of https://github.com/clicon/clixon
This commit is contained in:
commit
a529546c88
45 changed files with 1913 additions and 708 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -45,5 +45,12 @@ build-root/*.tar.xz
|
||||||
build-root/*.rpm
|
build-root/*.rpm
|
||||||
build-root/rpmbuild
|
build-root/rpmbuild
|
||||||
|
|
||||||
|
util/clixon_util_datastore
|
||||||
|
util/clixon_util_json
|
||||||
|
util/clixon_util_stream
|
||||||
|
util/clixon_util_xml
|
||||||
|
util/clixon_util_xpath
|
||||||
|
util/clixon_util_yang
|
||||||
|
|
||||||
test/site.sh
|
test/site.sh
|
||||||
doc/html
|
doc/html
|
||||||
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -5,10 +5,28 @@
|
||||||
## 3.10.0 (Upcoming)
|
## 3.10.0 (Upcoming)
|
||||||
|
|
||||||
### Major New features
|
### 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)
|
### API changes on existing features (you may need to change your code)
|
||||||
|
* Removed obsolete `CLICON_CLI_MODEL_TREENAME_PATCH`
|
||||||
|
|
||||||
### Minor changes
|
### Minor changes
|
||||||
|
* Added specific clixon_suberrno code: XMLPARSE_ERRNO to identify XML parse errors.
|
||||||
|
|
||||||
### Corrected Bugs
|
### Corrected Bugs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ APPSRC += backend_socket.c
|
||||||
APPSRC += backend_client.c
|
APPSRC += backend_client.c
|
||||||
APPSRC += backend_commit.c
|
APPSRC += backend_commit.c
|
||||||
APPSRC += backend_plugin.c
|
APPSRC += backend_plugin.c
|
||||||
|
APPSRC += backend_startup.c
|
||||||
APPOBJ = $(APPSRC:.c=.o)
|
APPOBJ = $(APPSRC:.c=.o)
|
||||||
|
|
||||||
# Accessible from plugin
|
# Accessible from plugin
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@
|
||||||
#include <clixon/clixon.h>
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
#include "clixon_backend_handle.h"
|
#include "clixon_backend_handle.h"
|
||||||
#include "backend_commit.h"
|
|
||||||
#include "backend_plugin.h"
|
#include "backend_plugin.h"
|
||||||
|
#include "backend_commit.h"
|
||||||
#include "backend_client.h"
|
#include "backend_client.h"
|
||||||
#include "backend_handle.h"
|
#include "backend_handle.h"
|
||||||
|
|
||||||
|
|
@ -208,7 +208,7 @@ from_client_get_config(clicon_handle h,
|
||||||
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
||||||
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||||
xpath="/";
|
xpath="/";
|
||||||
if (xmldb_get(h, db, xpath, 1, &xret) < 0){
|
if (xmldb_get(h, db, xpath, 1, &xret, NULL) < 0){
|
||||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
@ -335,7 +335,7 @@ client_statedata(clicon_handle h,
|
||||||
if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
|
if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
|
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;
|
goto done;
|
||||||
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
|
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -391,7 +391,7 @@ from_client_get(clicon_handle h,
|
||||||
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||||
xpath="/";
|
xpath="/";
|
||||||
/* Get config */
|
/* Get config */
|
||||||
if (xmldb_get(h, "running", xpath, 0, &xret) < 0){
|
if (xmldb_get(h, "running", xpath, 0, &xret, NULL) < 0){
|
||||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,9 @@ generic_validate(yang_spec *yspec,
|
||||||
goto done;
|
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] h Clicon handle
|
||||||
* @param[in] candidate The candidate database. The wanted backend state
|
* @param[in] candidate The candidate database. The wanted backend state
|
||||||
* @retval -1 Error - or validation failed (but cbret not set)
|
* @retval -1 Error - or validation failed (but cbret not set)
|
||||||
|
|
@ -165,12 +167,9 @@ validate_common(clicon_handle h,
|
||||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* 2. Parse xml trees
|
|
||||||
* This is the state we are going from */
|
|
||||||
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
|
|
||||||
goto done;
|
|
||||||
/* This is the state we are going to */
|
/* 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;
|
goto done;
|
||||||
|
|
||||||
/* Validate the target state. It is not completely clear this should be done
|
/* Validate the target state. It is not completely clear this should be done
|
||||||
|
|
@ -183,6 +182,11 @@ validate_common(clicon_handle h,
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
/* 2. Parse xml trees
|
||||||
|
* This is the state we are going from */
|
||||||
|
if (xmldb_get(h, "running", "/", 1, &td->td_src, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
/* 3. Compute differences */
|
/* 3. Compute differences */
|
||||||
if (xml_diff(yspec,
|
if (xml_diff(yspec,
|
||||||
td->td_src,
|
td->td_src,
|
||||||
|
|
@ -244,6 +248,90 @@ validate_common(clicon_handle h,
|
||||||
goto done;
|
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
|
/*! Do a diff between candidate and running, then start a commit transaction
|
||||||
*
|
*
|
||||||
* The code reverts changes if the commit fails. But if the revert
|
* The code reverts changes if the commit fails. But if the revert
|
||||||
|
|
@ -298,7 +386,10 @@ candidate_commit(clicon_handle h,
|
||||||
/* 9. Call plugin transaction end callbacks */
|
/* 9. Call plugin transaction end callbacks */
|
||||||
plugin_transaction_end(h, td);
|
plugin_transaction_end(h, td);
|
||||||
|
|
||||||
/* 8. Copy running back to candidate in case end functions updated running */
|
/* 8. Copy running back to candidate in case end functions updated running
|
||||||
|
* XXX: room for improvement: candidate and running may be equal.
|
||||||
|
* Copy only diffs?
|
||||||
|
*/
|
||||||
if (xmldb_copy(h, "running", candidate) < 0){
|
if (xmldb_copy(h, "running", candidate) < 0){
|
||||||
/* ignore errors or signal major setback ? */
|
/* ignore errors or signal major setback ? */
|
||||||
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
|
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_commit(clicon_handle h, int pid, cbuf *cbret);
|
||||||
int from_client_discard_changes(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);
|
int candidate_commit(clicon_handle h, char *db, cbuf *cbret);
|
||||||
|
|
||||||
#endif /* _BACKEND_COMMIT_H_ */
|
#endif /* _BACKEND_COMMIT_H_ */
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,10 @@
|
||||||
#include "clixon_backend_handle.h"
|
#include "clixon_backend_handle.h"
|
||||||
#include "backend_socket.h"
|
#include "backend_socket.h"
|
||||||
#include "backend_client.h"
|
#include "backend_client.h"
|
||||||
#include "backend_commit.h"
|
|
||||||
#include "backend_plugin.h"
|
#include "backend_plugin.h"
|
||||||
|
#include "backend_commit.h"
|
||||||
#include "backend_handle.h"
|
#include "backend_handle.h"
|
||||||
|
#include "backend_startup.h"
|
||||||
|
|
||||||
/* Command line options to be passed to getopt(3) */
|
/* 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:"
|
#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() */
|
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
|
/*! Create backend server socket and register callback
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @retval s Server socket file descriptor (see socket(2))
|
* @retval s Server socket file descriptor (see socket(2))
|
||||||
|
|
@ -305,256 +228,88 @@ nacm_load_external(clicon_handle h)
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Merge xml in filename into database
|
/*! Given a retval, transform to status or fatal error
|
||||||
* @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
|
|
||||||
*
|
*
|
||||||
OK:
|
* @param[in] ret Return value from xml validation function
|
||||||
copy reset commit merge
|
* @param[out] status Transform status according to rules below
|
||||||
running----+ |--------------------+--------+------>
|
* @retval 0 OK, status set
|
||||||
\ / /
|
* @retval -1 Fatal error outside scope of startup_status
|
||||||
candidate +--------------------+ /
|
* Transformation rules:
|
||||||
/
|
* 1) retval -1 assume clicon_errno/suberrno set. Special case from xml parser
|
||||||
tmp |-------+-----+------------+---|
|
* is clicon_suberrno = XMLPARSE_ERRNO which assumes an XML (non-fatal) parse
|
||||||
reset extra file
|
* error which translates to -> STARTUP_ERR
|
||||||
|
* All other error cases translates to fatal error
|
||||||
COMMIT ERROR:
|
* 2) retval 0 is xml validation fails -> STARTUP_INVALID
|
||||||
copy reset copy
|
* 3) retval 1 is OK -> STARTUP_OK
|
||||||
running----+ |--------------------+------> EXIT
|
* 4) any other retval translates to fatal error
|
||||||
\ /
|
|
||||||
candidate +--------------------+
|
|
||||||
|
|
||||||
* @note: if commit fails, copy candidate to running and exit
|
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
startup_mode_running(clicon_handle h,
|
ret2status(int ret,
|
||||||
char *extraxml_file)
|
enum startup_status *status)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cbuf *cbret = NULL;
|
|
||||||
|
|
||||||
if ((cbret = cbuf_new()) == NULL){
|
switch (ret){
|
||||||
clicon_err(OE_XML, errno, "cbuf_new");
|
case -1:
|
||||||
|
if (clicon_suberrno != XMLPARSE_ERRNO)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
clicon_err_reset();
|
||||||
/* Stash original running to candidate for later commit */
|
*status = STARTUP_ERR;
|
||||||
if (xmldb_copy(h, "running", "candidate") < 0)
|
break;
|
||||||
goto done;
|
case 0:
|
||||||
/* Load plugins and call plugin_init() */
|
*status = STARTUP_INVALID;
|
||||||
if (backend_plugin_initiate(h) != 0)
|
break;
|
||||||
goto done;
|
case 1:
|
||||||
/* Clear tmp db */
|
*status = STARTUP_OK;
|
||||||
if (db_reset(h, "tmp") < 0)
|
break;
|
||||||
goto done;
|
default:
|
||||||
/* Application may define extra xml in its reset function*/
|
clicon_err(OE_CFG, EINVAL, "No such retval %d", retval);
|
||||||
if (clixon_plugin_reset(h, "tmp") < 0)
|
} /* switch */
|
||||||
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;
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
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;
|
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
|
/*! usage
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
*/
|
||||||
static int
|
static void
|
||||||
startup_mode_startup(clicon_handle h,
|
usage(clicon_handle h,
|
||||||
char *extraxml_file)
|
char *argv0)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
char *plgdir = clicon_backend_dir(h);
|
||||||
cbuf *cbret = NULL;
|
char *confsock = clicon_sock(h);
|
||||||
|
char *confpid = clicon_backend_pidfile(h);
|
||||||
|
char *group = clicon_sock_group(h);
|
||||||
|
|
||||||
/* Create return buffer for netconf xml errors */
|
fprintf(stderr, "usage:%s <options>*\n"
|
||||||
if ((cbret = cbuf_new()) == NULL){
|
"where options are\n"
|
||||||
clicon_err(OE_XML, errno, "cbuf_new");
|
"\t-h\t\tHelp\n"
|
||||||
goto done;
|
"\t-D <level>\tDebug level\n"
|
||||||
}
|
"\t-f <file>\tCLICON config file\n"
|
||||||
/* Stash original running to backup */
|
"\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"
|
||||||
if (xmldb_copy(h, "running", "backup") < 0)
|
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
|
||||||
goto done;
|
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
|
||||||
/* If startup does not exist, clear it */
|
"\t-b <dir>\tSpecify XMLDB database directory\n"
|
||||||
if (xmldb_exists(h, "startup") != 1) /* diff */
|
"\t-F\t\tRun in foreground, do not run as daemon\n"
|
||||||
if (xmldb_create(h, "startup") < 0) /* diff */
|
"\t-z\t\tKill other config daemon and exit\n"
|
||||||
return -1;
|
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
|
||||||
/* Load plugins and call plugin_init() */
|
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)(default: %s)\n"
|
||||||
if (backend_plugin_initiate(h) != 0)
|
"\t-P <file>\tPid filename (default: %s)\n"
|
||||||
goto done;
|
"\t-1\t\tRun once and then quit (dont wait for events)\n"
|
||||||
/* Clear tmp db */
|
"\t-s <mode>\tSpecify backend startup mode: none|startup|running|init)\n"
|
||||||
if (db_reset(h, "tmp") < 0)
|
"\t-c <file>\tLoad extra xml configuration, but don't commit.\n"
|
||||||
goto done;
|
"\t-g <group>\tClient membership required to this group (default: %s)\n"
|
||||||
/* 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 startup */
|
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
|
||||||
if (candidate_commit(h, "startup", cbret) < 1) /* diff */
|
"\t-x <plugin>\tXMLDB plugin\n"
|
||||||
goto fail;
|
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
|
||||||
/* Merge user reset state and extra xml file (no commit) */
|
argv0,
|
||||||
if (db_merge(h, "tmp", "running", cbret) < 1)
|
plgdir ? plgdir : "none",
|
||||||
goto fail;
|
confsock ? confsock : "none",
|
||||||
retval = 0;
|
confpid ? confpid : "none",
|
||||||
done:
|
group ? group : "none"
|
||||||
/* 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");
|
exit(-1);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
@ -586,6 +341,9 @@ main(int argc,
|
||||||
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
||||||
char *str;
|
char *str;
|
||||||
int ss = -1; /* server socket */
|
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 */
|
/* In the startup, logs to stderr & syslog and debug flag set later */
|
||||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||||
|
|
@ -796,7 +554,7 @@ main(int argc,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group_name2gid(config_group, NULL) < 0){
|
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"
|
"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"
|
"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:"
|
"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)
|
stream_publish_init() < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
if (xmldb_plugin_load(h, xmldb_plugin) < 0)
|
if (xmldb_plugin_load(h, xmldb_plugin) < 0)
|
||||||
|
|
@ -876,10 +634,14 @@ main(int argc,
|
||||||
goto done;
|
goto done;
|
||||||
if (xmldb_setopt(h, "nacm_xtree", (void*)clicon_nacm_ext(h)) < 0)
|
if (xmldb_setopt(h, "nacm_xtree", (void*)clicon_nacm_ext(h)) < 0)
|
||||||
goto done;
|
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 needs to be defined, */
|
||||||
startup_mode = clicon_startup_mode(h);
|
startup_mode = clicon_startup_mode(h);
|
||||||
if (startup_mode == -1){
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
/* Init running db if it is not there
|
/* Init running db if it is not there
|
||||||
|
|
@ -887,27 +649,59 @@ main(int argc,
|
||||||
if (xmldb_exists(h, "running") != 1)
|
if (xmldb_exists(h, "running") != 1)
|
||||||
if (xmldb_create(h, "running") < 0)
|
if (xmldb_create(h, "running") < 0)
|
||||||
return -1;
|
return -1;
|
||||||
switch (startup_mode){
|
/* If startup fails, lib functions report invalidation info in a cbuf */
|
||||||
case SM_NONE:
|
if ((cbret = cbuf_new()) == NULL){
|
||||||
if (startup_mode_none(h) < 0)
|
clicon_err(OE_XML, errno, "cbuf_new");
|
||||||
goto done;
|
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. */
|
/* Initiate the shared candidate. */
|
||||||
if (xmldb_copy(h, "running", "candidate") < 0)
|
if (xmldb_copy(h, "running", "candidate") < 0)
|
||||||
goto done;
|
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 */
|
/* Call backend plugin_start with user -- options */
|
||||||
if (plugin_start_useroptions(h, argv0, argc, argv) <0)
|
if (plugin_start_useroptions(h, argv0, argc, argv) <0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -952,6 +746,8 @@ main(int argc,
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (cbret)
|
||||||
|
cbuf_free(cbret);
|
||||||
clicon_log(LOG_NOTICE, "%s: %u Terminated retval:%d", __PROGRAM__, getpid(), retval);
|
clicon_log(LOG_NOTICE, "%s: %u Terminated retval:%d", __PROGRAM__, getpid(), retval);
|
||||||
backend_terminate(h); /* Cannot use h after this */
|
backend_terminate(h); /* Cannot use h after this */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,31 @@ clixon_plugin_statedata(clicon_handle h,
|
||||||
return retval;
|
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 */
|
/*! Create and initialize transaction */
|
||||||
transaction_data_t *
|
transaction_data_t *
|
||||||
transaction_new(void)
|
transaction_new(void)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ int backend_plugin_initiate(clicon_handle h);
|
||||||
int clixon_plugin_reset(clicon_handle h, char *db);
|
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_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);
|
transaction_data_t * transaction_new(void);
|
||||||
int transaction_free(transaction_data_t *);
|
int transaction_free(transaction_data_t *);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ config_socket_init_unix(clicon_handle h,
|
||||||
return -1;
|
return -1;
|
||||||
#if 0
|
#if 0
|
||||||
if (gid == 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
|
#endif
|
||||||
/* create unix socket */
|
/* create unix socket */
|
||||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
||||||
|
|
|
||||||
340
apps/backend/backend_startup.c
Normal file
340
apps/backend/backend_startup.c
Normal 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;
|
||||||
|
}
|
||||||
49
apps/backend/backend_startup.h
Normal file
49
apps/backend/backend_startup.h
Normal 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_ */
|
||||||
|
|
@ -483,7 +483,7 @@ main(int argc, char **argv)
|
||||||
free(restarg);
|
free(restarg);
|
||||||
// Gets in your face if we log on stderr
|
// Gets in your face if we log on stderr
|
||||||
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no 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)
|
if (h)
|
||||||
cli_terminate(h);
|
cli_terminate(h);
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,6 @@ main(int argc,
|
||||||
done:
|
done:
|
||||||
netconf_terminate(h);
|
netconf_terminate(h);
|
||||||
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no 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());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ struct text_handle {
|
||||||
int th_pretty; /* Store xml/json pretty-printed. */
|
int th_pretty; /* Store xml/json pretty-printed. */
|
||||||
char *th_nacm_mode;
|
char *th_nacm_mode;
|
||||||
cxobj *th_nacm_xtree;
|
cxobj *th_nacm_xtree;
|
||||||
|
cxobj *th_modst; /* According to RFC7895 for rev mgmnt */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Struct per database in hash */
|
/* Struct per database in hash */
|
||||||
|
|
@ -211,6 +212,8 @@ text_disconnect(xmldb_handle xh)
|
||||||
}
|
}
|
||||||
if (th->th_nacm_mode)
|
if (th->th_nacm_mode)
|
||||||
free(th->th_nacm_mode);
|
free(th->th_nacm_mode);
|
||||||
|
if (th->th_modst)
|
||||||
|
xml_free(th->th_modst);
|
||||||
free(th);
|
free(th);
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -247,6 +250,8 @@ text_getopt(xmldb_handle xh,
|
||||||
*value = &th->th_nacm_mode;
|
*value = &th->th_nacm_mode;
|
||||||
else if (strcmp(optname, "nacm_xtree") == 0)
|
else if (strcmp(optname, "nacm_xtree") == 0)
|
||||||
*value = th->th_nacm_xtree;
|
*value = th->th_nacm_xtree;
|
||||||
|
else if (strcmp(optname, "modules_state") == 0)
|
||||||
|
*value = th->th_modst;
|
||||||
else{
|
else{
|
||||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -305,6 +310,9 @@ text_setopt(xmldb_handle xh,
|
||||||
else if (strcmp(optname, "nacm_xtree") == 0){
|
else if (strcmp(optname, "nacm_xtree") == 0){
|
||||||
th->th_nacm_xtree = (cxobj*)value;
|
th->th_nacm_xtree = (cxobj*)value;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(optname, "modules_state") == 0){
|
||||||
|
th->th_modst = (cxobj*)value;
|
||||||
|
}
|
||||||
else{
|
else{
|
||||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -352,13 +360,14 @@ singleconfigroot(cxobj *xt,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
int
|
|
||||||
text_get_nocache(struct text_handle *th,
|
text_get_nocache(struct text_handle *th,
|
||||||
const char *db,
|
const char *db,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
int config,
|
int config,
|
||||||
cxobj **xtop)
|
cxobj **xtop,
|
||||||
|
cxobj **xms)
|
||||||
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *dbfile = NULL;
|
char *dbfile = NULL;
|
||||||
|
|
@ -460,79 +469,225 @@ text_get_nocache(struct text_handle *th,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Given XML tree x0 with marked nodes, copy marked nodes to new tree x1
|
||||||
/*! Given a tree rooted in x0t and a point x0, create a duplicate path in x1t
|
* Two marks are used: XML_FLAG_MARK and XML_FLAG_CHANGE
|
||||||
* Given a tree rooted in x0t and a point in the tree x0,
|
*
|
||||||
* create a duplicate rooted in x1t.
|
* The algorithm works as following:
|
||||||
* x0t -- x -- x -- x0
|
* (1) Copy individual nodes marked with XML_FLAG_CHANGE
|
||||||
* x1t
|
* until nodes marked with XML_FLAG_MARK are reached, where
|
||||||
|
* (2) the complete subtree of that node is copied.
|
||||||
|
* (3) Special case: key nodes in lists are copied if any node in list is marked
|
||||||
|
* @note you may want to check:!yang_config(ys)
|
||||||
*/
|
*/
|
||||||
int
|
static int
|
||||||
create_path2root(cxobj *x0,
|
xml_copy_marked(cxobj *x0,
|
||||||
cxobj *x0t,
|
cxobj *x1)
|
||||||
cxobj *x1t,
|
|
||||||
cxobj **x1r)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
cxobj *x0p;
|
|
||||||
cxobj *x1p = NULL;
|
|
||||||
cxobj *x1 = NULL;
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
name = xml_name(x0);
|
|
||||||
x0p = xml_parent(x0);
|
|
||||||
if (x0t != x0p){
|
|
||||||
if (create_path2root(x0p, x0t, x1t, &x1p) < 0)
|
|
||||||
goto done;
|
|
||||||
if ((x1 = xml_find_type(x1p, NULL, name, CX_ELMNT)) == NULL){
|
|
||||||
if ((x1 = xml_new(name, x1p, xml_spec(x0))) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (xml_copy_one(x0, x1) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else /* root of tree */
|
|
||||||
x1 = x1t;
|
|
||||||
assert(x1);
|
|
||||||
*x1r = x1;
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @param[in] config If set only configuration data, else also state
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
xml_copy_config(cxobj *x0,
|
|
||||||
cxobj *x1,
|
|
||||||
int config)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
int mark;
|
||||||
cxobj *x;
|
cxobj *x;
|
||||||
cxobj *xcopy;
|
cxobj *xcopy;
|
||||||
yang_stmt *ys;
|
int iskey;
|
||||||
|
yang_stmt *yt;
|
||||||
|
char *name;
|
||||||
|
|
||||||
if (xml_copy_one(x0, x1) <0)
|
assert(x0 && x1);
|
||||||
goto done;
|
yt = xml_spec(x0); /* can be null */
|
||||||
|
/* Copy all attributes */
|
||||||
x = NULL;
|
x = NULL;
|
||||||
while ((x = xml_child_each(x0, x, -1)) != NULL) {
|
while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {
|
||||||
if (config){
|
name = xml_name(x);
|
||||||
if ((ys = (yang_stmt*)xml_spec(x)) != NULL){
|
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
|
||||||
if (!yang_config(ys))
|
goto done;
|
||||||
continue; /* skip */
|
if (xml_copy(x, xcopy) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Go through children to detect any marked nodes:
|
||||||
|
* (3) Special case: key nodes in lists are copied if any
|
||||||
|
* node in list is marked
|
||||||
|
*/
|
||||||
|
mark = 0;
|
||||||
|
x = NULL;
|
||||||
|
while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) {
|
||||||
|
if (xml_flag(x, XML_FLAG_MARK|XML_FLAG_CHANGE)){
|
||||||
|
mark++;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((xcopy = xml_new(xml_name(x), x1, xml_spec(x))) == NULL)
|
x = NULL;
|
||||||
|
while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) {
|
||||||
|
name = xml_name(x);
|
||||||
|
if (xml_flag(x, XML_FLAG_MARK)){
|
||||||
|
/* (2) the complete subtree of that node is copied. */
|
||||||
|
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (xml_copy(x, xcopy) < 0) /* recursion */
|
if (xml_copy(x, xcopy) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (xml_flag(x, XML_FLAG_CHANGE)){
|
||||||
|
/* Copy individual nodes marked with XML_FLAG_CHANGE */
|
||||||
|
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (xml_copy_marked(x, xcopy) < 0) /* */
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* (3) Special case: key nodes in lists are copied if any
|
||||||
|
* node in list is marked */
|
||||||
|
if (mark && yt && yt->ys_keyword == Y_LIST){
|
||||||
|
/* XXX: I think yang_key_match is suboptimal here */
|
||||||
|
if ((iskey = yang_key_match((yang_node*)yt, name)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (iskey){
|
||||||
|
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (xml_copy(x, xcopy) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
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
|
/*! 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
|
* The function returns a minimal tree that includes all sub-trees that match
|
||||||
|
|
@ -543,6 +698,7 @@ xml_copy_config(cxobj *x0,
|
||||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||||
* @param[in] config If set only configuration data, else also state
|
* @param[in] config If set only configuration data, else also state
|
||||||
* @param[out] xret Single return XML tree. Free with xml_free()
|
* @param[out] xret Single return XML tree. Free with xml_free()
|
||||||
|
* @param[out] xms If set, return modules-state differences
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @see xmldb_get the generic API function
|
* @see xmldb_get the generic API function
|
||||||
|
|
@ -552,20 +708,18 @@ text_get_cache(struct text_handle *th,
|
||||||
const char *db,
|
const char *db,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
int config,
|
int config,
|
||||||
cxobj **xtop)
|
cxobj **xtop,
|
||||||
|
cxobj **xms)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *dbfile = NULL;
|
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
cxobj *x0t = NULL; /* (cached) top of tree */
|
cxobj *x0t = NULL; /* (cached) top of tree */
|
||||||
cxobj *x;
|
cxobj *x0;
|
||||||
int fd = -1;
|
|
||||||
cxobj **xvec = NULL;
|
cxobj **xvec = NULL;
|
||||||
size_t xlen;
|
size_t xlen;
|
||||||
int i;
|
int i;
|
||||||
struct db_element *de = NULL;
|
struct db_element *de = NULL;
|
||||||
cxobj *x1t = NULL;
|
cxobj *x1t = NULL;
|
||||||
cxobj *x1;
|
|
||||||
struct db_element de0 = {0,};
|
struct db_element de0 = {0,};
|
||||||
|
|
||||||
if ((yspec = th->th_yangspec) == NULL){
|
if ((yspec = th->th_yangspec) == NULL){
|
||||||
|
|
@ -574,45 +728,18 @@ text_get_cache(struct text_handle *th,
|
||||||
}
|
}
|
||||||
de = hash_value(th->th_dbs, db, NULL);
|
de = hash_value(th->th_dbs, db, NULL);
|
||||||
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
||||||
if (text_db2file(th, db, &dbfile) < 0)
|
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||||
goto done;
|
if (text_readfile(th, db, yspec, &x0t, xms) < 0)
|
||||||
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, &x0t)) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else if ((xml_parse_file(fd, "</config>", yspec, &x0t)) < 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(x0t) == 0){
|
|
||||||
if (xml_name_set(x0t, "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(x0t, &x0t) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* default values? */
|
|
||||||
if (xml_apply(x0t, CX_ELMNT, xml_default, NULL) < 0)
|
|
||||||
goto done;
|
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?
|
||||||
|
*/
|
||||||
de0.de_xml = x0t;
|
de0.de_xml = x0t;
|
||||||
hash_add(th->th_dbs, db, &de0, sizeof(de0));
|
hash_add(th->th_dbs, db, &de0, sizeof(de0));
|
||||||
} /* x0t == NULL */
|
} /* x0t == NULL */
|
||||||
else
|
else
|
||||||
x0t = de->de_xml;
|
x0t = de->de_xml;
|
||||||
|
|
||||||
/* Here x0t looks like: <config>...</config> */
|
/* Here x0t looks like: <config>...</config> */
|
||||||
/* Given the xpath, return a vector of matches in xvec
|
/* Given the xpath, return a vector of matches in xvec
|
||||||
* Can we do everything in one go?
|
* Can we do everything in one go?
|
||||||
|
|
@ -622,31 +749,32 @@ text_get_cache(struct text_handle *th,
|
||||||
* a) for every node that is found, copy to new tree
|
* a) for every node that is found, copy to new tree
|
||||||
* b) if config dont dont state data
|
* b) if config dont dont state data
|
||||||
*/
|
*/
|
||||||
/* Make new tree by copying top-of-tree from x0t to x1t */
|
|
||||||
if ((x1t = xml_new(xml_name(x0t), NULL, xml_spec(x0t))) == NULL)
|
/* Here xt looks like: <config>...</config> */
|
||||||
goto done;
|
|
||||||
if (xml_copy_one(x0t, x1t) < 0)
|
|
||||||
goto done;
|
|
||||||
/* Get matches */
|
|
||||||
if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Iterate through matches */
|
/* Make new tree by copying top-of-tree from x0t to x1t */
|
||||||
|
if ((x1t = xml_new(xml_name(x0t), NULL, xml_spec(x0t))) == NULL)
|
||||||
/* If vectors are specified then mark the nodes found with all ancestors
|
goto done;
|
||||||
* and filter out everything else,
|
/* Iterate through the match vector
|
||||||
* otherwise return complete tree.
|
* For every node found in x0, mark the tree up to t1
|
||||||
*/
|
*/
|
||||||
for (i=0; i<xlen; i++){
|
for (i=0; i<xlen; i++){
|
||||||
/* Copy x and its ancestors to x1 */
|
x0 = xvec[i];
|
||||||
x = xvec[i];
|
xml_flag_set(x0, XML_FLAG_MARK);
|
||||||
x1 = NULL;
|
xml_apply_ancestor(x0, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
||||||
if (create_path2root(x, x0t, x1t, &x1) < 0)
|
|
||||||
goto done;
|
|
||||||
assert(x1);
|
|
||||||
if (xml_copy_config(x, x1, config) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
if (xml_copy_marked(x0t, x1t) < 0) /* config */
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(x0t, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(x1t, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
|
||||||
|
goto done;
|
||||||
|
/* x1t is wrong here should be <config><system>.. but is <system>.. */
|
||||||
|
/* XXX where should we apply default values once? */
|
||||||
|
if (xml_apply(x1t, CX_ELMNT, xml_default, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
/* Copy the matching parts of the (relevant) XML tree.
|
/* Copy the matching parts of the (relevant) XML tree.
|
||||||
* If cache was empty, also update to datastore cache
|
* If cache was empty, also update to datastore cache
|
||||||
|
|
@ -656,12 +784,8 @@ text_get_cache(struct text_handle *th,
|
||||||
*xtop = x1t;
|
*xtop = x1t;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (dbfile)
|
|
||||||
free(dbfile);
|
|
||||||
if (xvec)
|
if (xvec)
|
||||||
free(xvec);
|
free(xvec);
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -683,17 +807,17 @@ text_get(xmldb_handle xh,
|
||||||
const char *db,
|
const char *db,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
int config,
|
int config,
|
||||||
cxobj **xtop)
|
cxobj **xtop,
|
||||||
|
cxobj **xms)
|
||||||
{
|
{
|
||||||
struct text_handle *th = handle(xh);
|
struct text_handle *th = handle(xh);
|
||||||
|
|
||||||
if (th->th_cache)
|
if (th->th_cache)
|
||||||
return text_get_cache(th, db, xpath, config, xtop);
|
return text_get_cache(th, db, xpath, config, xtop, xms);
|
||||||
else
|
else
|
||||||
return text_get_nocache(th, db, xpath, config, xtop);
|
return text_get_nocache(th, db, xpath, config, xtop, xms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
|
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
|
||||||
* @param[in] th text handle
|
* @param[in] th text handle
|
||||||
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
||||||
|
|
@ -1180,7 +1304,6 @@ text_put(xmldb_handle xh,
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
struct text_handle *th = handle(xh);
|
struct text_handle *th = handle(xh);
|
||||||
char *dbfile = NULL;
|
char *dbfile = NULL;
|
||||||
int fd = -1;
|
|
||||||
FILE *f = NULL;
|
FILE *f = NULL;
|
||||||
cbuf *cb = NULL;
|
cbuf *cb = NULL;
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
|
|
@ -1188,6 +1311,9 @@ text_put(xmldb_handle xh,
|
||||||
struct db_element *de = NULL;
|
struct db_element *de = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
cxobj *xnacm = NULL;
|
cxobj *xnacm = NULL;
|
||||||
|
char *mode;
|
||||||
|
cxobj *xnacm0 = NULL;
|
||||||
|
cxobj *xmodst = NULL;
|
||||||
|
|
||||||
if (cbret == NULL){
|
if (cbret == NULL){
|
||||||
clicon_err(OE_XML, EINVAL, "cbret is NULL");
|
clicon_err(OE_XML, EINVAL, "cbret is NULL");
|
||||||
|
|
@ -1206,53 +1332,22 @@ text_put(xmldb_handle xh,
|
||||||
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
|
if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
|
||||||
x0 = de->de_xml;
|
x0 = de->de_xml;
|
||||||
}
|
}
|
||||||
|
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||||
if (x0 == NULL){
|
if (x0 == NULL){
|
||||||
if (text_db2file(th, db, &dbfile) < 0)
|
if (text_readfile(th, db, yspec, &x0, NULL) < 0)
|
||||||
goto done;
|
|
||||||
if (dbfile==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "dbfile NULL");
|
|
||||||
goto done;
|
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){
|
if (strcmp(xml_name(x0),"config")!=0){
|
||||||
clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
|
clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
|
||||||
xml_name(x0));
|
xml_name(x0));
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
/* Here x0 looks like: <config>...</config> */
|
||||||
|
|
||||||
#if 0 /* debug */
|
#if 0 /* debug */
|
||||||
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
|
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
|
||||||
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
|
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
|
||||||
#endif
|
#endif
|
||||||
#if 1
|
|
||||||
{
|
|
||||||
char *mode;
|
|
||||||
cxobj *xnacm0 = NULL;
|
|
||||||
|
|
||||||
mode = th->th_nacm_mode;
|
mode = th->th_nacm_mode;
|
||||||
if (mode){
|
if (mode){
|
||||||
if (strcmp(mode, "external")==0)
|
if (strcmp(mode, "external")==0)
|
||||||
|
|
@ -1267,9 +1362,7 @@ text_put(xmldb_handle xh,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* Here assume if xnacm is set (actually may be ret==0?) do NACM */
|
/* 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
|
* Modify base tree x with modification x1. This is where the
|
||||||
* new tree is made.
|
* new tree is made.
|
||||||
|
|
@ -1306,17 +1399,19 @@ text_put(xmldb_handle xh,
|
||||||
hash_add(th->th_dbs, db, &de0, sizeof(de0));
|
hash_add(th->th_dbs, db, &de0, sizeof(de0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dbfile == NULL){
|
|
||||||
if (text_db2file(th, db, &dbfile) < 0)
|
if (text_db2file(th, db, &dbfile) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (dbfile==NULL){
|
if (dbfile==NULL){
|
||||||
clicon_err(OE_XML, 0, "dbfile NULL");
|
clicon_err(OE_XML, 0, "dbfile NULL");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
/* Add module revision info before writing to file)
|
||||||
if (fd != -1){
|
*/
|
||||||
close(fd);
|
if (th->th_modst){
|
||||||
fd = -1;
|
if ((xmodst = xml_dup(th->th_modst)) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (xml_addsub(x0, xmodst) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
if ((f = fopen(dbfile, "w")) == NULL){
|
if ((f = fopen(dbfile, "w")) == NULL){
|
||||||
clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
|
clicon_err(OE_CFG, errno, "Creating file %s", dbfile);
|
||||||
|
|
@ -1328,14 +1423,16 @@ text_put(xmldb_handle xh,
|
||||||
}
|
}
|
||||||
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
|
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
/* Remove modules state after writing to file
|
||||||
|
*/
|
||||||
|
if (xmodst && xml_purge(xmodst) < 0)
|
||||||
|
goto done;
|
||||||
retval = 1;
|
retval = 1;
|
||||||
done:
|
done:
|
||||||
if (f != NULL)
|
if (f != NULL)
|
||||||
fclose(f);
|
fclose(f);
|
||||||
if (dbfile)
|
if (dbfile)
|
||||||
free(dbfile);
|
free(dbfile);
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
if (cb)
|
if (cb)
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
if (!th->th_cache && x0)
|
if (!th->th_cache && x0)
|
||||||
|
|
@ -1362,42 +1459,51 @@ text_copy(xmldb_handle xh,
|
||||||
struct text_handle *th = handle(xh);
|
struct text_handle *th = handle(xh);
|
||||||
char *fromfile = NULL;
|
char *fromfile = NULL;
|
||||||
char *tofile = NULL;
|
char *tofile = NULL;
|
||||||
struct db_element *de = NULL;
|
struct db_element *de1 = NULL;
|
||||||
struct db_element *de2 = NULL;
|
struct db_element *de2 = NULL;
|
||||||
|
struct db_element de0 = {0,};
|
||||||
|
cxobj *x1 = NULL;
|
||||||
|
cxobj *x2 = NULL;
|
||||||
|
|
||||||
/* XXX lock */
|
/* XXX lock */
|
||||||
if (th->th_cache){
|
if (th->th_cache){
|
||||||
/* 1. Free xml tree in "to"
|
/* 1. "to" xml tree in x1 */
|
||||||
*/
|
if ((de1 = hash_value(th->th_dbs, from, NULL)) != NULL)
|
||||||
if ((de = hash_value(th->th_dbs, to, NULL)) != NULL){
|
x1 = de1->de_xml;
|
||||||
if (de->de_xml != NULL){
|
if ((de2 = hash_value(th->th_dbs, to, NULL)) != NULL)
|
||||||
xml_free(de->de_xml);
|
x2 = de2->de_xml;
|
||||||
de->de_xml = NULL;
|
if (x1 == NULL && x2 == NULL){
|
||||||
|
/* do nothing */
|
||||||
}
|
}
|
||||||
|
else if (x1 == NULL){ /* free x2 and set to NULL */
|
||||||
|
xml_free(x2);
|
||||||
|
x2 = NULL;
|
||||||
}
|
}
|
||||||
/* 2. Copy xml tree from "from" to "to"
|
else if (x2 == NULL){ /* create x2 and copy x1 to it */
|
||||||
* 2a) create "to" if it does not exist
|
if ((x2 = xml_new(xml_name(x1), NULL, xml_spec(x1))) == NULL)
|
||||||
*/
|
|
||||||
if ((de2 = hash_value(th->th_dbs, from, NULL)) != NULL){
|
|
||||||
if (de2->de_xml != NULL){
|
|
||||||
struct db_element de0 = {0,};
|
|
||||||
cxobj *x, *xcopy;
|
|
||||||
x = de2->de_xml;
|
|
||||||
if (de != NULL)
|
|
||||||
de0 = *de;
|
|
||||||
if ((xcopy = xml_new(xml_name(x), NULL, xml_spec(x))) == NULL)
|
|
||||||
goto done;
|
goto done;
|
||||||
if (xml_copy(x, xcopy) < 0)
|
if (xml_copy(x1, x2) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
de0.de_xml = xcopy;
|
}
|
||||||
|
else{ /* copy x1 to x2 */
|
||||||
|
xml_free(x2);
|
||||||
|
if ((x2 = xml_new(xml_name(x1), NULL, xml_spec(x1))) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (xml_copy(x1, x2) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (x1 || x2){
|
||||||
|
if (de2)
|
||||||
|
de0 = *de2;
|
||||||
|
de0.de_xml = x2; /* The new tree */
|
||||||
hash_add(th->th_dbs, to, &de0, sizeof(de0));
|
hash_add(th->th_dbs, to, &de0, sizeof(de0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (text_db2file(th, from, &fromfile) < 0)
|
if (text_db2file(th, from, &fromfile) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (text_db2file(th, to, &tofile) < 0)
|
if (text_db2file(th, to, &tofile) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
/* Copy the files themselves (above only in-memory cache) */
|
||||||
if (clicon_file_copy(fromfile, tofile) < 0)
|
if (clicon_file_copy(fromfile, tofile) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -1589,7 +1695,7 @@ text_create(xmldb_handle xh,
|
||||||
struct db_element *de = NULL;
|
struct db_element *de = NULL;
|
||||||
cxobj *xt = 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 ((de = hash_value(th->th_dbs, db, NULL)) != NULL){
|
||||||
if ((xt = de->de_xml) != NULL){
|
if ((xt = de->de_xml) != NULL){
|
||||||
assert(xt==NULL); /* XXX */
|
assert(xt==NULL); /* XXX */
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* 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 **xms);
|
||||||
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
|
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_dump(FILE *f, char *dbfilename, char *rxkey);
|
||||||
int text_copy(xmldb_handle h, const char *from, const char *to);
|
int text_copy(xmldb_handle h, const char *from, const char *to);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ APIs. There are currently plugins for: CLI, Netconf, Restconf, the datastore an
|
||||||
|
|
||||||
## Which programming language is used?
|
## Which programming language is used?
|
||||||
Clixon is written in C. The plugins are written in C. The CLI
|
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?
|
## How to best understand Clixon?
|
||||||
Run the Clixon example, in the [example](../example) directory.
|
Run the Clixon example, in the [example](../example) directory.
|
||||||
|
|
|
||||||
|
|
@ -288,8 +288,9 @@ static clixon_plugin_api api = {
|
||||||
clixon_plugin_init,
|
clixon_plugin_init,
|
||||||
plugin_start,
|
plugin_start,
|
||||||
plugin_exit,
|
plugin_exit,
|
||||||
.ca_reset=plugin_reset,/* reset */
|
.ca_reset=plugin_reset,/* reset for extra XML at startup*/
|
||||||
.ca_statedata=plugin_statedata, /* statedata */
|
.ca_statedata=plugin_statedata, /* statedata */
|
||||||
|
.ca_upgrade=example_upgrade, /* upgrade configuration */
|
||||||
.ca_trans_begin=NULL, /* trans begin */
|
.ca_trans_begin=NULL, /* trans begin */
|
||||||
.ca_trans_validate=transaction_validate,/* trans validate */
|
.ca_trans_validate=transaction_validate,/* trans validate */
|
||||||
.ca_trans_complete=NULL, /* trans complete */
|
.ca_trans_complete=NULL, /* trans complete */
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <syslog.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
/* clicon */
|
/* clicon */
|
||||||
|
|
@ -50,6 +51,13 @@
|
||||||
/* These include signatures for plugin and transaction callbacks. */
|
/* These include signatures for plugin and transaction callbacks. */
|
||||||
#include <clixon/clixon_backend.h>
|
#include <clixon/clixon_backend.h>
|
||||||
|
|
||||||
|
/* Variable to control 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 */
|
/* forward */
|
||||||
static int example_stream_timer_setup(clicon_handle h);
|
static int example_stream_timer_setup(clicon_handle h);
|
||||||
|
|
||||||
|
|
@ -212,6 +220,25 @@ example_statedata(clicon_handle h,
|
||||||
return retval;
|
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.
|
/*! Plugin state reset. Add xml or set state in backend machine.
|
||||||
* Called in each backend plugin. plugin_reset is called after all plugins
|
* Called in each backend plugin. plugin_reset is called after all plugins
|
||||||
* have been initialized. This give the application a chance to reset
|
* have been initialized. This give the application a chance to reset
|
||||||
|
|
@ -233,6 +260,7 @@ example_reset(clicon_handle h,
|
||||||
int ret;
|
int ret;
|
||||||
cbuf *cbret = NULL;
|
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>"
|
if (xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface>"
|
||||||
"<name>lo</name><type>ex:loopback</type>"
|
"<name>lo</name><type>ex:loopback</type>"
|
||||||
"</interface></interfaces></config>", NULL, &xt) < 0)
|
"</interface></interfaces></config>", NULL, &xt) < 0)
|
||||||
|
|
@ -252,6 +280,7 @@ example_reset(clicon_handle h,
|
||||||
cbuf_get(cbret));
|
cbuf_get(cbret));
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (cbret)
|
if (cbret)
|
||||||
|
|
@ -267,7 +296,8 @@ example_reset(clicon_handle h,
|
||||||
* @param[in] argv Argument vector
|
* @param[in] argv Argument vector
|
||||||
*
|
*
|
||||||
* plugin_start is called once everything has been initialized, right before
|
* 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
|
* plugins by using "-- <args>" where <args> is any choice of
|
||||||
* options specific to the application. These options are passed to the
|
* options specific to the application. These options are passed to the
|
||||||
* plugin_start function via the argc and argv arguments which
|
* plugin_start function via the argc and argv arguments which
|
||||||
|
|
@ -278,6 +308,16 @@ example_start(clicon_handle h,
|
||||||
int argc,
|
int argc,
|
||||||
char **argv)
|
char **argv)
|
||||||
{
|
{
|
||||||
|
char c;
|
||||||
|
|
||||||
|
opterr = 0;
|
||||||
|
optind = 1;
|
||||||
|
while ((c = getopt(argc, argv, "r")) != -1)
|
||||||
|
switch (c) {
|
||||||
|
case 'r':
|
||||||
|
_reset = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,6 +336,7 @@ static clixon_plugin_api api = {
|
||||||
example_exit, /* exit */
|
example_exit, /* exit */
|
||||||
.ca_reset=example_reset, /* reset */
|
.ca_reset=example_reset, /* reset */
|
||||||
.ca_statedata=example_statedata, /* statedata */
|
.ca_statedata=example_statedata, /* statedata */
|
||||||
|
.ca_upgrade=example_upgrade, /* upgrade configuration */
|
||||||
.ca_trans_begin=NULL, /* trans begin */
|
.ca_trans_begin=NULL, /* trans begin */
|
||||||
.ca_trans_validate=transaction_validate,/* trans validate */
|
.ca_trans_validate=transaction_validate,/* trans validate */
|
||||||
.ca_trans_complete=NULL, /* trans complete */
|
.ca_trans_complete=NULL, /* trans complete */
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ clixon_plugin_init(clicon_handle h)
|
||||||
clicon_debug(1, "%s backend nacm", __FUNCTION__);
|
clicon_debug(1, "%s backend nacm", __FUNCTION__);
|
||||||
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||||
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
|
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
|
||||||
clicon_log(LOG_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 NULL;
|
||||||
}
|
}
|
||||||
return &api;
|
return &api;
|
||||||
|
|
|
||||||
|
|
@ -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
|
/* Full xmlns validation check is made only if XML has associated YANG spec
|
||||||
*/
|
*/
|
||||||
#define XMLNS_YANG_ONLY 1
|
#define XMLNS_YANG_ONLY 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@
|
||||||
*/
|
*/
|
||||||
#define ERR_STRLEN 256
|
#define ERR_STRLEN 256
|
||||||
|
|
||||||
|
/* Special error number for clicon_suberrno
|
||||||
|
* For catching xml parse errors as exceptions
|
||||||
|
*/
|
||||||
|
#define XMLPARSE_ERRNO 898943
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Types
|
* Types
|
||||||
* Add error here, but must also add an entry in EV variable.
|
* Add error here, but must also add an entry in EV variable.
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,10 @@ int clicon_xmldb_handle_set(clicon_handle h, void *xh);
|
||||||
char *clicon_username_get(clicon_handle h);
|
char *clicon_username_get(clicon_handle h);
|
||||||
int clicon_username_set(clicon_handle h, void *username);
|
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 */
|
/* Set and get socket fd (ie backend server socket / restconf fcgx socket */
|
||||||
int clicon_socket_get(clicon_handle h);
|
int clicon_socket_get(clicon_handle h);
|
||||||
int clicon_socket_set(clicon_handle h, int s);
|
int clicon_socket_set(clicon_handle h, int s);
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ typedef void *transaction_data;
|
||||||
/* Transaction callbacks */
|
/* Transaction callbacks */
|
||||||
typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
|
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
|
* Format prompt before each getline
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] mode Cligen syntax mode
|
* @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);
|
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
|
/* plugin init struct for the api
|
||||||
* Note: Implicit init function
|
* Note: Implicit init function
|
||||||
*/
|
*/
|
||||||
|
|
@ -139,14 +160,16 @@ struct clixon_plugin_api{
|
||||||
struct {
|
struct {
|
||||||
} cau_netconf;
|
} cau_netconf;
|
||||||
struct {
|
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) */
|
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_begin; /* Transaction start */
|
||||||
trans_cb_t *cb_trans_validate; /* Transaction validation */
|
trans_cb_t *cb_trans_validate; /* Transaction validation */
|
||||||
trans_cb_t *cb_trans_complete; /* Transaction validation complete */
|
trans_cb_t *cb_trans_complete; /* Transaction validation complete */
|
||||||
trans_cb_t *cb_trans_commit; /* Transaction commit */
|
trans_cb_t *cb_trans_commit; /* Transaction commit */
|
||||||
trans_cb_t *cb_trans_end; /* Transaction completed */
|
trans_cb_t *cb_trans_end; /* Transaction completed */
|
||||||
trans_cb_t *cb_trans_abort; /* Transaction aborted */
|
trans_cb_t *cb_trans_abort; /* Transaction aborted */
|
||||||
|
|
||||||
} cau_backend;
|
} cau_backend;
|
||||||
|
|
||||||
} u;
|
} u;
|
||||||
|
|
@ -158,6 +181,7 @@ struct clixon_plugin_api{
|
||||||
#define ca_auth u.cau_restconf.cr_auth
|
#define ca_auth u.cau_restconf.cr_auth
|
||||||
#define ca_reset u.cau_backend.cb_reset
|
#define ca_reset u.cau_backend.cb_reset
|
||||||
#define ca_statedata u.cau_backend.cb_statedata
|
#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_begin u.cau_backend.cb_trans_begin
|
||||||
#define ca_trans_validate u.cau_backend.cb_trans_validate
|
#define ca_trans_validate u.cau_backend.cb_trans_validate
|
||||||
#define ca_trans_complete u.cau_backend.cb_trans_complete
|
#define ca_trans_complete u.cau_backend.cb_trans_complete
|
||||||
|
|
|
||||||
|
|
@ -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);
|
typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
|
||||||
|
|
||||||
/* Type of xmldb get function */
|
/* 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 */
|
/* 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);
|
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_disconnect(clicon_handle h);
|
||||||
int xmldb_getopt(clicon_handle h, char *optname, void **value);
|
int xmldb_getopt(clicon_handle h, char *optname, void **value);
|
||||||
int xmldb_setopt(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_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_copy(clicon_handle h, const char *from, const char *to);
|
||||||
int xmldb_lock(clicon_handle h, const char *db, int pid);
|
int xmldb_lock(clicon_handle h, const char *db, int pid);
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@
|
||||||
int modules_state_cache_set(clicon_handle h, cxobj *msx);
|
int modules_state_cache_set(clicon_handle h, cxobj *msx);
|
||||||
int yang_modules_init(clicon_handle h);
|
int yang_modules_init(clicon_handle h);
|
||||||
char *yang_modules_revision(clicon_handle h);
|
char *yang_modules_revision(clicon_handle h);
|
||||||
|
|
||||||
int yang_modules_state_get(clicon_handle h, yang_spec *yspec, char *xpath,
|
int yang_modules_state_get(clicon_handle h, yang_spec *yspec, char *xpath,
|
||||||
cxobj **xret);
|
int brief, cxobj **xret);
|
||||||
|
|
||||||
#endif /* _CLIXON_YANG_MODULE_H_ */
|
#endif /* _CLIXON_YANG_MODULE_H_ */
|
||||||
|
|
|
||||||
|
|
@ -878,7 +878,7 @@ nacm_access_pre(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(mode, "internal")==0){
|
else if (strcmp(mode, "internal")==0){
|
||||||
if (xmldb_get(h, "running", "nacm", 0, &xnacm0) < 0)
|
if (xmldb_get(h, "running", "nacm", 0, &xnacm0, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -889,6 +889,36 @@ clicon_username_set(clicon_handle h,
|
||||||
return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0;
|
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)
|
/*! Get socket fd (ie backend server socket / restconf fcgx socket)
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ plugin_load_one(clicon_handle h,
|
||||||
clicon_err_reset();
|
clicon_err_reset();
|
||||||
if ((api = initfn(h)) == NULL) {
|
if ((api = initfn(h)) == NULL) {
|
||||||
if (!clicon_errno){ /* if clicon_err() is not called then log and continue */
|
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);
|
dlclose(handle);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
|
||||||
|
|
@ -133,10 +133,13 @@ struct xml{
|
||||||
/*
|
/*
|
||||||
* Variables
|
* 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.
|
* or rpc if no xmlns attribute specifies namespace.
|
||||||
* This is loose semantics of finding namespaces.
|
* This is _wrong_, but is the way Clixon originally was written, and some
|
||||||
* And it is wrong, but is the way Clixon originally was written."
|
* code still relies on it.
|
||||||
|
* This, of course, should change.
|
||||||
* @see CLICON_XML_NS_STRICT clixon configure option
|
* @see CLICON_XML_NS_STRICT clixon configure option
|
||||||
*/
|
*/
|
||||||
int _CLICON_XML_NS_STRICT = 1;
|
int _CLICON_XML_NS_STRICT = 1;
|
||||||
|
|
@ -1129,6 +1132,7 @@ xml_find_type_value(cxobj *xt,
|
||||||
* @code
|
* @code
|
||||||
* cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR);
|
* cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR);
|
||||||
* @endcode
|
* @endcode
|
||||||
|
* @see xml_find which finds any child given name
|
||||||
* @see xml_find_value where a body can be found as well
|
* @see xml_find_value where a body can be found as well
|
||||||
*/
|
*/
|
||||||
cxobj *
|
cxobj *
|
||||||
|
|
@ -1708,7 +1712,7 @@ xml_parse_file(int fd,
|
||||||
* cxobj *xt = NULL;
|
* cxobj *xt = NULL;
|
||||||
* if (xml_parse_string(str, yspec, &xt) < 0)
|
* if (xml_parse_string(str, yspec, &xt) < 0)
|
||||||
* err;
|
* 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;
|
* err;
|
||||||
* @endcode
|
* @endcode
|
||||||
* @see xml_parse_file
|
* @see xml_parse_file
|
||||||
|
|
@ -1778,7 +1782,11 @@ xml_parse_va(cxobj **xtop,
|
||||||
return retval;
|
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
|
int
|
||||||
xml_copy_one(cxobj *x0,
|
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,
|
* x1 should be a created placeholder. If x1 is non-empty,
|
||||||
* the copied tree is appended to the existing tree.
|
* 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
|
* @code
|
||||||
* x1 = xml_new("new", xparent, NULL);
|
* x1 = xml_new("new", xparent, NULL);
|
||||||
* if (xml_copy(x0, x1) < 0)
|
* if (xml_copy(x0, x1) < 0)
|
||||||
|
|
|
||||||
|
|
@ -325,11 +325,12 @@ xmldb_setopt(clicon_handle h,
|
||||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||||
* @param[in] config If set only configuration data, else also state
|
* @param[in] config If set only configuration data, else also state
|
||||||
* @param[out] xret Single return XML tree. Free with xml_free()
|
* @param[out] xret Single return XML tree. Free with xml_free()
|
||||||
|
* @param[out] xms If set, return modules-state differences
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @code
|
* @code
|
||||||
* cxobj *xt;
|
* 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;
|
* err;
|
||||||
* xml_free(xt);
|
* xml_free(xt);
|
||||||
* @endcode
|
* @endcode
|
||||||
|
|
@ -341,7 +342,8 @@ xmldb_get(clicon_handle h,
|
||||||
const char *db,
|
const char *db,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
int config,
|
int config,
|
||||||
cxobj **xret)
|
cxobj **xret,
|
||||||
|
cxobj **xms)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
xmldb_handle xh;
|
xmldb_handle xh;
|
||||||
|
|
@ -359,7 +361,7 @@ xmldb_get(clicon_handle h,
|
||||||
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
|
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
|
||||||
goto done;
|
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 DEBUG
|
||||||
if (retval == 0) {
|
if (retval == 0) {
|
||||||
cbuf *cb = cbuf_new();
|
cbuf *cb = cbuf_new();
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,10 @@ xml2cli(FILE *f,
|
||||||
if (ys->ys_keyword == Y_LIST){
|
if (ys->ys_keyword == Y_LIST){
|
||||||
if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0)
|
if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (match)
|
if (match){
|
||||||
continue;
|
fprintf(f, "%s\n", cbuf_get(cbpre));
|
||||||
|
continue; /* Not key itself */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
|
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -1572,7 +1574,7 @@ xml_tree_prune_flagged(cxobj *xt,
|
||||||
cxobj *xprev;
|
cxobj *xprev;
|
||||||
|
|
||||||
x = NULL;
|
x = NULL;
|
||||||
xprev = x = NULL;
|
xprev = NULL;
|
||||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||||
if (xml_flag(x, flag) == test?flag:0){ /* Pass test means purge */
|
if (xml_flag(x, flag) == test?flag:0){ /* Pass test means purge */
|
||||||
if (xml_purge(x) < 0)
|
if (xml_purge(x) < 0)
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
void
|
void
|
||||||
clixon_xml_parseerror(void *_ya, char *s)
|
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);
|
_YA->ya_linenum, s, clixon_xml_parsetext);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya,
|
||||||
char *ver)
|
char *ver)
|
||||||
{
|
{
|
||||||
if(strcmp(ver, "1.0")){
|
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);
|
free(ver);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -224,12 +224,12 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
|
||||||
cxobj *xc;
|
cxobj *xc;
|
||||||
|
|
||||||
if (strcmp(xml_name(x), name)){
|
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);
|
xml_name(x), name);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (xml_prefix(x)!=NULL){
|
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);
|
xml_prefix(x), xml_name(x), name);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +267,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
|
||||||
cxobj *xc;
|
cxobj *xc;
|
||||||
|
|
||||||
if (strcmp(xml_name(x), name)){
|
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_prefix(x),
|
||||||
xml_name(x),
|
xml_name(x),
|
||||||
namespace,
|
namespace,
|
||||||
|
|
@ -276,7 +276,7 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
|
||||||
}
|
}
|
||||||
if (xml_prefix(x)==NULL ||
|
if (xml_prefix(x)==NULL ||
|
||||||
strcmp(xml_prefix(x), namespace)){
|
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_prefix(x),
|
||||||
xml_name(x),
|
xml_name(x),
|
||||||
namespace,
|
namespace,
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ modules_state_cache_set(clicon_handle h,
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] yspec Yang spec
|
* @param[in] yspec Yang spec
|
||||||
* @param[in] xpath XML Xpath
|
* @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
|
* @param[in,out] xret Existing XML tree, merge x into this
|
||||||
* @retval -1 Error (fatal)
|
* @retval -1 Error (fatal)
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
|
|
@ -212,10 +213,149 @@ x +--ro namespace inet:uri
|
||||||
+--ro schema? inet:uri
|
+--ro schema? inet:uri
|
||||||
* @see netconf_create_hello
|
* @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
|
int
|
||||||
yang_modules_state_get(clicon_handle h,
|
yang_modules_state_get(clicon_handle h,
|
||||||
yang_spec *yspec,
|
yang_spec *yspec,
|
||||||
char *xpath,
|
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)
|
cxobj **xret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
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,"<modules-state xmlns=\"%s\">", yns->ys_argument);
|
||||||
cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
|
cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
ymod = NULL;
|
ymod = NULL;
|
||||||
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
|
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
|
||||||
if (ymod->ys_keyword != Y_MODULE &&
|
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);
|
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
|
||||||
else
|
else
|
||||||
cprintf(cb,"<revision></revision>");
|
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)
|
if ((ys = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) != NULL)
|
||||||
cprintf(cb,"<namespace>%s</namespace>", ys->ys_argument);
|
cprintf(cb,"<namespace>%s</namespace>", ys->ys_argument);
|
||||||
else
|
else
|
||||||
cprintf(cb,"<namespace></namespace>");
|
cprintf(cb,"<namespace></namespace>");
|
||||||
|
<<<<<<< HEAD
|
||||||
/* This follows order in rfc 7895: feature, conformance-type, submodules */
|
/* This follows order in rfc 7895: feature, conformance-type, submodules */
|
||||||
yc = NULL;
|
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) {
|
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
|
||||||
switch(yc->ys_keyword){
|
switch(yc->ys_keyword){
|
||||||
case Y_FEATURE:
|
case Y_FEATURE:
|
||||||
|
|
@ -286,6 +448,7 @@ yang_modules_state_get(clicon_handle h,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
cprintf(cb, "<conformance-type>implement</conformance-type>");
|
cprintf(cb, "<conformance-type>implement</conformance-type>");
|
||||||
yc = NULL;
|
yc = NULL;
|
||||||
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
|
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
|
||||||
|
|
@ -302,6 +465,25 @@ yang_modules_state_get(clicon_handle h,
|
||||||
default:
|
default:
|
||||||
break;
|
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>");
|
cprintf(cb,"</module>");
|
||||||
}
|
}
|
||||||
|
|
@ -326,3 +508,4 @@ yang_modules_state_get(clicon_handle h,
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ fi
|
||||||
err=0
|
err=0
|
||||||
testnr=0
|
testnr=0
|
||||||
for test in test*.sh; do
|
for test in test*.sh; do
|
||||||
|
if [ $testnr != 0 ]; then echo; fi
|
||||||
testfile=$test
|
testfile=$test
|
||||||
. ./$test
|
. ./$test
|
||||||
errcode=$?
|
errcode=$?
|
||||||
|
|
|
||||||
19
test/lib.sh
19
test/lib.sh
|
|
@ -37,9 +37,12 @@ if [ -x ./site.sh ]; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Running test number
|
# Test number from start
|
||||||
: ${testnr:=0}
|
: ${testnr:=0}
|
||||||
|
|
||||||
|
# Test number in this test
|
||||||
|
testi=0
|
||||||
|
|
||||||
# Single test. Set by "new"
|
# Single test. Set by "new"
|
||||||
testname=
|
testname=
|
||||||
|
|
||||||
|
|
@ -59,6 +62,10 @@ testname=
|
||||||
# If set to 0, override starting of clixon_backend in test (you bring your own)
|
# If set to 0, override starting of clixon_backend in test (you bring your own)
|
||||||
: ${BE:=1}
|
: ${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)
|
# If set, enable debugging (of backend)
|
||||||
: ${DBG:=0}
|
: ${DBG:=0}
|
||||||
|
|
||||||
|
|
@ -95,7 +102,12 @@ dir=/var/tmp/$0
|
||||||
if [ ! -d $dir ]; then
|
if [ ! -d $dir ]; then
|
||||||
mkdir $dir
|
mkdir $dir
|
||||||
fi
|
fi
|
||||||
rm -rf $dir/*
|
# 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,
|
# error and exit,
|
||||||
# arg1: expected
|
# arg1: expected
|
||||||
|
|
@ -178,8 +190,9 @@ new(){
|
||||||
checkvalgrind
|
checkvalgrind
|
||||||
fi
|
fi
|
||||||
testnr=`expr $testnr + 1`
|
testnr=`expr $testnr + 1`
|
||||||
|
testi=`expr $testi + 1`
|
||||||
testname=$1
|
testname=$1
|
||||||
>&2 echo "Test$testnr [$1]"
|
>&2 echo "Test $testi($testnr) [$1]"
|
||||||
}
|
}
|
||||||
|
|
||||||
# clixon command tester.
|
# clixon command tester.
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ new "cli configure set interfaces"
|
||||||
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$"
|
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$"
|
||||||
|
|
||||||
new "cli show configuration"
|
new "cli show configuration"
|
||||||
expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^interfaces interface eth/0/0 enabled true"
|
expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 '^interfaces interface eth/0/0 interfaces interface eth/0/0 enabled true'
|
||||||
|
|
||||||
new "cli configure using encoded chars data <&"
|
new "cli configure using encoded chars data <&"
|
||||||
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 ""
|
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 ""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Test of backward compatibility
|
# Test of backward compatibility, from last release to newer.
|
||||||
# 1) Load <3.9 startup/running/extra files without namespaces - ensure it returns namespaces
|
|
||||||
#
|
#
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
|
|
@ -35,82 +34,6 @@ cat <<EOF > $cfg
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
testrun(){
|
# Nothing
|
||||||
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>'
|
|
||||||
|
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
|
|
|
||||||
178
test/test_copy_config.sh
Executable file
178
test/test_copy_config.sh
Executable 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
|
||||||
|
|
@ -155,7 +155,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '<rpc-reply><data
|
||||||
|
|
||||||
# Check as file
|
# Check as file
|
||||||
new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted."
|
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"
|
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>]]>]]>$'
|
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>]]>]]>$'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Startup test: Start clicon daemon in the (four) different startup modes
|
# 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
|
# - The example reset_state callback adds "lo" interface
|
||||||
# - An extra xml configuration file starts with an "extra" interface
|
# - An extra xml configuration file starts with an "extra" interface
|
||||||
# - running db starts with a "run" 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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
<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_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
|
||||||
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
||||||
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
|
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
|
||||||
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
||||||
|
|
@ -36,12 +36,17 @@ cat <<EOF > $cfg
|
||||||
|
|
||||||
EOF
|
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(){
|
testrun(){
|
||||||
mode=$1
|
mode=$1
|
||||||
expect=$2
|
expect=$2
|
||||||
|
|
||||||
dbdir=$dir/db
|
# Create running-db containin the interface "run"
|
||||||
cat <<EOF > $dbdir
|
sudo rm -f $dir/running_db
|
||||||
|
cat <<EOF > $dir/running_db
|
||||||
<config>
|
<config>
|
||||||
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||||
<interface>
|
<interface>
|
||||||
|
|
@ -51,9 +56,10 @@ testrun(){
|
||||||
</interfaces>
|
</interfaces>
|
||||||
</config>
|
</config>
|
||||||
EOF
|
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>
|
<config>
|
||||||
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||||
<interface>
|
<interface>
|
||||||
|
|
@ -63,8 +69,9 @@ EOF
|
||||||
</interfaces>
|
</interfaces>
|
||||||
</config>
|
</config>
|
||||||
EOF
|
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
|
cat <<EOF > $dir/config
|
||||||
<config>
|
<config>
|
||||||
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||||
|
|
@ -76,20 +83,23 @@ EOF
|
||||||
</config>
|
</config>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
if [ $BE -ne 0 ]; then # Bring your own backend
|
||||||
# kill old backend (if any)
|
# kill old backend (if any)
|
||||||
new "kill old backend"
|
new "kill old backend"
|
||||||
sudo clixon_backend -zf $cfg
|
sudo clixon_backend -zf $cfg
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "start backend -f $cfg -s $mode -c $dir/config"
|
new "start backend -f $cfg -s $mode -c $dir/config"
|
||||||
start_backend -s $mode -f $cfg -c $dir/config
|
start_backend -s $mode -f $cfg -c $dir/config
|
||||||
|
|
||||||
new "waiting"
|
new "waiting"
|
||||||
sleep $RCWAIT
|
sleep $RCWAIT
|
||||||
|
else
|
||||||
new "Check $mode"
|
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>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' "^<rpc-reply>$expect</rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "Kill backend"
|
new "Kill backend"
|
||||||
|
|
@ -102,12 +112,12 @@ EOF
|
||||||
stop_backend -f $cfg
|
stop_backend -f $cfg
|
||||||
} # testrun
|
} # 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 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
|
rm -rf $dir
|
||||||
|
|
|
||||||
333
test/test_upgrade.sh
Executable file
333
test/test_upgrade.sh
Executable 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
|
||||||
|
|
@ -202,7 +202,7 @@ main(int argc, char **argv)
|
||||||
xpath = argv[1];
|
xpath = argv[1];
|
||||||
else
|
else
|
||||||
xpath = "/";
|
xpath = "/";
|
||||||
if (xmldb_get(h, db, xpath, 0, &xt) < 0)
|
if (xmldb_get(h, db, xpath, 0, &xt, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
clicon_xml2file(stdout, xt, 0, 0);
|
clicon_xml2file(stdout, xt, 0, 0);
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ main(int argc, char **argv)
|
||||||
else
|
else
|
||||||
xpath = "/";
|
xpath = "/";
|
||||||
for (i=0;i<nr;i++){
|
for (i=0;i<nr;i++){
|
||||||
if (xmldb_get(h, db, xpath, 0, &xt) < 0)
|
if (xmldb_get(h, db, xpath, 0, &xt, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (xt == NULL){
|
if (xt == NULL){
|
||||||
clicon_err(OE_DB, 0, "xt is NULL");
|
clicon_err(OE_DB, 0, "xt is NULL");
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,14 @@ module clixon-config {
|
||||||
If set, insert spaces and line-feeds making the XML/JSON human
|
If set, insert spaces and line-feeds making the XML/JSON human
|
||||||
readable. If not set, make the XML/JSON more compact.";
|
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 {
|
leaf CLICON_XML_NS_STRICT {
|
||||||
type boolean;
|
type boolean;
|
||||||
default true;
|
default true;
|
||||||
|
|
@ -474,5 +482,6 @@ module clixon-config {
|
||||||
data to store before dropping. 0 means no retention";
|
data to store before dropping. 0 means no retention";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue