Ensure non-modstate enabled backends ignore the modstate

This commit is contained in:
Olof hagsand 2019-02-28 16:03:23 +01:00
parent 31304380cd
commit 4d3c61735c
3 changed files with 160 additions and 105 deletions

View file

@ -318,6 +318,7 @@ startup_failsafe(clicon_handle h)
/*! Init modules state of the backend (server). To compare with startup XML /*! Init modules state of the backend (server). To compare with startup XML
* Set the modules state as setopt to the datastore module. * Set the modules state as setopt to the datastore module.
* Only if CLICON_XMLDB_MODSTATE is enabled
*/ */
int int
startup_module_state(clicon_handle h, startup_module_state(clicon_handle h,
@ -326,6 +327,8 @@ startup_module_state(clicon_handle h,
int retval = -1; int retval = -1;
cxobj *x = NULL; cxobj *x = NULL;
if (!clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
goto ok;
if (yang_modules_state_get(h, yspec, NULL, 1, &x) < 0) if (yang_modules_state_get(h, yspec, NULL, 1, &x) < 0)
goto done; goto done;
if (x){ if (x){
@ -334,6 +337,7 @@ startup_module_state(clicon_handle h,
if (xmldb_setopt(h, "modules_state", (void*)x) < 0) if (xmldb_setopt(h, "modules_state", (void*)x) < 0)
goto done; goto done;
} }
ok:
retval = 0; retval = 0;
done: done:
return retval; return retval;

View file

@ -90,7 +90,9 @@ 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 */ cxobj *th_modst; /* According to RFC7895 for rev mgmnt
* Should be set only if CLICON_XMLDB_MODSTATE is true
*/
}; };
/* Struct per database in hash */ /* Struct per database in hash */
@ -552,63 +554,15 @@ xml_copy_marked(cxobj *x0,
return retval; return retval;
} }
/*! Common read function that reads an XML tree from file /*! Read module-state in an XML tree
* @param[in] db Symbolic database name, eg "candidate", "running" *
* @param[out] xp XML tree read from file * @param[in] th Datastore text handle
* @param[in] yspec Top-level yang spec
* @param[in] xt XML tree
* @param[out] xmsp If set, return modules-state differences * @param[out] xmsp If set, return modules-state differences
*/ *
static int * Read mst (module-state-tree) from xml tree (if any) and compare it with
text_readfile(struct text_handle *th, * the system state mst.
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: * This can happen:
* 1) There is no modules-state info in the file * 1) There is no modules-state info in the file
* 2) There is module state info in the file * 2) There is module state info in the file
@ -617,11 +571,26 @@ text_readfile(struct text_handle *th,
* 3b) File module-state matches system * 3b) File module-state matches system
* 3c) File module-state does not match system * 3c) File module-state does not match system
*/ */
if ((xmodst = xml_find_type(x0, NULL, "modules-state", CX_ELMNT)) == NULL){ static int
text_read_modstate(struct text_handle *th,
yang_spec *yspec,
cxobj *xt,
cxobj **xmsp)
{
int retval = -1;
cxobj *xmodst;
cxobj *xm = NULL;
cxobj *xm2;
cxobj *xs;
char *name; /* module name */
char *mrev; /* file revision */
char *srev; /* system revision */
cxobj *xmsd = NULL; /* Local modules-state diff tree */
if ((xmodst = xml_find_type(xt, NULL, "modules-state", CX_ELMNT)) == NULL){
/* 1) There is no modules-state info in the file */ /* 1) There is no modules-state info in the file */
} }
else{ else if (th->th_modst){
/* Create a diff tree */ /* Create a diff tree */
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &xmsd) < 0) if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &xmsd) < 0)
goto done; goto done;
@ -662,16 +631,14 @@ text_readfile(struct text_handle *th,
} }
} }
} }
/* The module-state is removed from the input XML tree. This is done
* in all cases, whether CLICON_XMLDB_MODSTATE is on or not.
* Clixon systems with CLICON_XMLDB_MODSTATE disabled ignores it
*/
if (xmodst){ if (xmodst){
/* For now ignore the modules_state - just remove it*/
if (xml_purge(xmodst) < 0) if (xml_purge(xmodst) < 0)
goto done; goto done;
} }
}
if (xp){
*xp = x0;
x0 = NULL;
}
if (xmsp){ if (xmsp){
*xmsp = xmsd; *xmsp = xmsd;
xmsd = NULL; xmsd = NULL;
@ -680,6 +647,69 @@ text_readfile(struct text_handle *th,
done: done:
if (xmsd) if (xmsd)
xml_free(xmsd); xml_free(xmsd);
return retval;
}
/*! Common read function that reads an XML tree from file
* @param[in] th Datastore text handle
* @param[in] db Symbolic database name, eg "candidate", "running"
* @param[in] yspec Top-level yang spec
* @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;
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;
}
/* From Clixon 3.10,datastore files may contain module-state defining
* which modules are used in the file.
*/
if (text_read_modstate(th, yspec, x0, xmsp) < 0)
goto done;
if (xp){
*xp = x0;
x0 = NULL;
}
retval = 0;
done:
if (fd != -1) if (fd != -1)
close(fd); close(fd);
if (dbfile) if (dbfile)
@ -693,8 +723,8 @@ text_readfile(struct text_handle *th,
* 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
* xpath. * xpath.
* This is a clixon datastore plugin of the the xmldb api * This is a clixon datastore plugin of the the xmldb api
* @param[in] h Clicon handle * @param[in] th Datastore text handle
* @param[in] dbname Name of database to search in (filename including dir path * @param[in] db Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] 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()
@ -793,8 +823,8 @@ text_get_cache(struct text_handle *th,
* 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
* xpath. * xpath.
* This is a clixon datastore plugin of the the xmldb api * This is a clixon datastore plugin of the the xmldb api
* @param[in] h Clicon handle * @param[in] th Datastore text handle
* @param[in] dbname Name of database to search in (filename including dir path * @param[in] db Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] 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()
@ -819,7 +849,7 @@ text_get(xmldb_handle xh,
} }
/*! 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 Datastore 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)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0 * @param[in] x0p Parent of x0
@ -1104,7 +1134,7 @@ text_modify(struct text_handle *th,
} /* text_modify */ } /* text_modify */
/*! Modify a top-level base tree x0 with modification tree x1 /*! Modify a top-level base tree x0 with modification tree x1
* @param[in] th text handle * @param[in] th Datastore 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)
* @param[in] x1 xml tree which modifies base * @param[in] x1 xml tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] yspec Top-level yang spec (if y is NULL)
@ -1406,6 +1436,7 @@ text_put(xmldb_handle xh,
goto done; goto done;
} }
/* Add module revision info before writing to file) /* Add module revision info before writing to file)
* Only if CLICON_XMLDB_MODSTATE is set
*/ */
if (th->th_modst){ if (th->th_modst){
if ((xmodst = xml_dup(th->th_modst)) == NULL) if ((xmodst = xml_dup(th->th_modst)) == NULL)

View file

@ -248,9 +248,10 @@ EOF
# mode is one of: init, none, running, or startup # mode is one of: init, none, running, or startup
# db is one of: running_db or startup_db # db is one of: running_db or startup_db
runtest(){ runtest(){
mode=$1 modstate=$1
expect=$2 mode=$2
startup=$3 expect=$3
startup=$4
new "test params: -f $cfg" new "test params: -f $cfg"
# Bring your own backend # Bring your own backend
@ -261,13 +262,13 @@ runtest(){
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend -s $mode -f $cfg" new "start backend -s $mode -f $cfg -o \"CLICON_XMLDB_MODSTATE=$modstate\""
start_backend -s $mode -f $cfg start_backend -s $mode -f $cfg -o "CLICON_XMLDB_MODSTATE=$modstate"
new "waiting" new "waiting"
sleep $RCWAIT sleep $RCWAIT
else else
new "Restart backend as eg follows: -Ff $cfg -s $mode ($BETIMEOUT s)" new "Restart backend as eg follows: -Ff $cfg -s $mode -o \"CLICON_XMLDB_MODSTATE=$modstate\" ($BETIMEOUT s)"
sleep $BETIMEOUT sleep $BETIMEOUT
fi fi
@ -292,43 +293,62 @@ runtest(){
# Compatible == all yang modules match # Compatible == all yang modules match
# runtest <mode> <expected running> <expected startup> # runtest <mode> <expected running> <expected startup>
new "1. Load compatible valid startup (all OK)" new "1. Run without CLICON_XMLDB_MODSTATE ensure no modstate in datastore"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases (cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-valid.xml startup_db) (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>' runtest false 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)" new "Verify no modstate in running"
expect="module"
ret=$(sudo grep $expect $dir/running_db)
if [ -n "$ret" ]; then
err "did not expect $expect" "$ret"
fi
new "2. 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 true 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 "Verify modstate in running"
expect="module"
ret=$(sudo grep $expect $dir/running_db)
if [ -z "$ret" ]; then
err "Expected $expect" "$ret"
fi
new "3. 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; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-valid.xml running_db) (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>' runtest true 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" new "4. Load non-compat valid startup"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases (cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-valid.xml startup_db) (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>' runtest true 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." new "5. 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; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-invalid.xml startup_db) (cd $dir; cp non-compat-invalid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>' runtest true startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>'
new "5. Load non-compat invalid running. Enter failsafe, startup invalid." new "6. 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; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp non-compat-invalid.xml running_db) (cd $dir; cp non-compat-invalid.xml running_db)
runtest running '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>' runtest true running '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>'
new "6. Load compatible invalid startup." new "7. Load compatible invalid startup."
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases (cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-invalid.xml startup_db) (cd $dir; cp compat-invalid.xml startup_db)
runtest startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>' runtest true startup '<data><a1 xmlns="urn:example:a">always work</a1></data>' '<data><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></data>'
# This testcase contains an error/exception of the clixon xml parser, and # This testcase contains an error/exception of the clixon xml parser, and
# I cant track down the memory leakage. # I cant track down the memory leakage.
if [ $valgrindtest -ne 2 ]; then if [ $valgrindtest -ne 2 ]; then
new "7. Load non-compat startup. Syntax fail, enter failsafe, startup invalid" new "8. 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; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-err.xml startup_db) (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>' runtest true 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>'
fi fi