xml changelog next iteration

This commit is contained in:
Olof hagsand 2019-03-26 12:04:51 +01:00
parent 1991c3870c
commit 8624be0a67
9 changed files with 532 additions and 353 deletions

View file

@ -140,6 +140,194 @@ generic_validate(yang_spec *yspec,
goto done; goto done;
} }
/*! Common startup validation
* Get db, upgrade it w potential transformed XML, populate it w yang spec,
* sort it, validate it by triggering a transaction
* and call application callback validations.
* @param[in] h Clicon handle
* @param[in] db The startup database. The wanted backend state
* @param[out] xtr Transformed XML
* @param[out] cbret CLIgen buffer w error stmt if retval = 0
* @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.
*/
static int
startup_common(clicon_handle h,
char *db,
transaction_data_t *td,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
modstate_diff_t *msd = NULL;
cxobj *xt = NULL;
/* If CLICON_XMLDB_MODSTATE is enabled, then get the db XML with
* potentially non-matching module-state in msd
*/
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msd = modstate_diff_new()) == NULL)
goto done;
if (xmldb_get(h, db, "/", 1, &xt, msd) < 0)
goto done;
if (msd){
if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, 0, "Yang spec not set");
goto done;
}
/* After upgrading, XML tree needs to be sorted and yang spec populated */
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
/* Handcraft transition with with only add tree */
td->td_target = xt;
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;
/* 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 (msd)
modstate_diff_free(msd);
return retval;
fail:
retval = 0;
goto done;
}
/*! Read startup db, check upgrades and validate it, return upgraded XML
*
* @param[in] h Clicon handle
* @param[in] db The startup database. The wanted backend state
* @param[out] xtr (Potentially) transformed XML
* @param[out] cbret CLIgen buffer w error stmt if retval = 0
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
int
startup_validate(clicon_handle h,
char *db,
cxobj **xtr,
cbuf *cbret)
{
int retval = -1;
int ret;
transaction_data_t *td = NULL;
/* Handcraft a transition with only target and add trees */
if ((td = transaction_new()) == NULL)
goto done;
if ((ret = startup_common(h, db, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
if (xtr){
*xtr = td->td_target;
td->td_target = NULL;
}
retval = 1;
done:
if (td)
transaction_free(td);
return retval;
fail: /* cbret should be set */
retval = 0;
goto done;
}
/*! Read startup db, check upgrades and commit it
*
* @param[in] h Clicon handle
* @param[in] db The startup database. The wanted backend state
* @param[out] cbret CLIgen buffer w error stmt if retval = 0
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
int
startup_commit(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
int ret;
transaction_data_t *td = NULL;
/* Handcraft a transition with only target and add trees */
if ((td = transaction_new()) == NULL)
goto done;
if ((ret = startup_common(h, db, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 8. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
goto done;
/* 9, write (potentially modified) tree to running
* XXX note here startup is copied to candidate, which may confuse everything
*/
if ((ret = xmldb_put(h, "running", OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 10. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 11. 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){
/* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
retval = 1;
done:
if (td)
transaction_free(td);
return retval;
fail: /* cbret should be set */
retval = 0;
goto done;
}
/*! Validate a candidate db and comnpare to running /*! Validate a candidate db and comnpare to running
* Get both source and dest datastore, validate target, compute diffs * Get both source and dest datastore, validate target, compute diffs
* and call application callback validations. * and call application callback validations.
@ -152,10 +340,10 @@ generic_validate(yang_spec *yspec,
* (only done for generic_validate) * (only done for generic_validate)
*/ */
static int static int
validate_common(clicon_handle h, from_validate_common(clicon_handle h,
char *candidate, char *candidate,
transaction_data_t *td, transaction_data_t *td,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
yang_spec *yspec; yang_spec *yspec;
@ -248,211 +436,6 @@ 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] db The startup database. The wanted backend state
* @param[out] xtr Transformed XML
* @param[out] cbret CLIgen buffer w error stmt if retval = 0
* @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,
cxobj **xtr,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
modstate_diff_t *msd = NULL;
cxobj *xt = 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
* Only if CLICON_XMLDB_MODSTATE is enabled
*/
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msd = modstate_diff_new()) == NULL)
goto done;
if (xmldb_get(h, db, "/", 1, &xt, msd) < 0)
goto done;
if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, 0, "Yang spec not set");
goto done;
}
/* After upgrading, XML tree needs to be sorted and yang spec populated */
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
/* Handcraft transition with with only add tree */
td->td_target = xt;
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;
/* 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;
if (xtr){
*xtr = td->td_target;
td->td_target = NULL;
}
retval = 1;
done:
if (td)
transaction_free(td);
if (msd)
modstate_diff_free(msd);
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;
}
int
startup_commit(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
modstate_diff_t *msd = NULL;
cxobj *xt = 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
* Only if CLICON_XMLDB_MODSTATE is enabled
*/
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msd = modstate_diff_new()) == NULL)
goto done;
if (xmldb_get(h, db, "/", 1, &xt, msd) < 0)
goto done;
if (msd){
if((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, 0, "Yang spec not set");
goto done;
}
/* After upgrading, XML tree needs to be sorted and yang spec populated */
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
/* Handcraft transition with with only add tree */
td->td_target = xt;
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;
/* 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;
/* 8. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
goto done;
/* 9, write (potentially modified) tree to running
* XXX note here startup is copied to candidate, which may confuse everything
*/
if ((ret = xmldb_put(h, "running", OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 10. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 11. 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){
/* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
retval = 1;
done:
if (td)
transaction_free(td);
if (msd)
modstate_diff_free(msd);
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
@ -482,7 +465,7 @@ candidate_commit(clicon_handle h,
/* Common steps (with validate). Load candidate and running and compute diffs /* Common steps (with validate). Load candidate and running and compute diffs
* Note this is only call that uses 3-values * Note this is only call that uses 3-values
*/ */
if ((ret = validate_common(h, candidate, td, cbret)) < 0) if ((ret = from_validate_common(h, candidate, td, cbret)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -697,7 +680,7 @@ from_client_validate(clicon_handle h,
if ((td = transaction_new()) == NULL) if ((td = transaction_new()) == NULL)
goto done; goto done;
/* Common steps (with commit) */ /* Common steps (with commit) */
if ((ret = validate_common(h, db, td, cbret)) < 1){ if ((ret = from_validate_common(h, db, td, cbret)) < 1){
clicon_debug(1, "Validate %s failed", db); clicon_debug(1, "Validate %s failed", db);
if (ret < 0){ if (ret < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)

View file

@ -249,8 +249,7 @@ example_statedata(clicon_handle h,
/*! Registered Upgrade callback function /*! Registered Upgrade callback function
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xn XML tree to be updated * @param[in] xn XML tree to be updated
* @param[in] modname Name of module * @param[in] ns Namespace of module (for info)
* @param[in] modns Namespace of module (for info)
* @param[in] from From revision on the form YYYYMMDD * @param[in] from From revision on the form YYYYMMDD
* @param[in] to To revision on the form YYYYMMDD (0 not in system) * @param[in] to To revision on the form YYYYMMDD (0 not in system)
* @param[in] arg User argument given at rpc_callback_register() * @param[in] arg User argument given at rpc_callback_register()
@ -466,11 +465,9 @@ clixon_plugin_init(clicon_handle h)
) < 0) ) < 0)
goto done; goto done;
/* General purpose upgrade callback */ /* General purpose upgrade callback */
if (upgrade_callback_register(h, 0?upgrade_all:xml_changelog_upgrade, if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 0)
NULL, 0, 0, NULL) < 0)
goto done; goto done;
/* Return plugin API */ /* Return plugin API */
return &api; return &api;
done: done:

View file

@ -201,6 +201,11 @@ struct clixon_plugin_api{
#define ca_trans_end u.cau_backend.cb_trans_end #define ca_trans_end u.cau_backend.cb_trans_end
#define ca_trans_abort u.cau_backend.cb_trans_abort #define ca_trans_abort u.cau_backend.cb_trans_abort
/*
* Macros
*/
#define upgrade_callback_register(h, cb, namespace, from, to, arg) upgrade_callback_reg_fn((h), (cb), #cb, (namespace), (from), (to), (arg))
typedef struct clixon_plugin_api clixon_plugin_api; typedef struct clixon_plugin_api clixon_plugin_api;
/* Internal plugin structure with dlopen() handle and plugin_api /* Internal plugin structure with dlopen() handle and plugin_api
@ -244,7 +249,7 @@ int rpc_callback_delete_all(void);
int rpc_callback_call(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg); int rpc_callback_call(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg);
/* upgrade callback API */ /* upgrade callback API */
int upgrade_callback_register(clicon_handle h, clicon_upgrade_cb cb, char *namespace, uint32_t from, uint32_t to, void *arg); int upgrade_callback_reg_fn(clicon_handle h, clicon_upgrade_cb cb, const char *strfn, char *namespace, uint32_t from, uint32_t to, void *arg);
int upgrade_callback_delete_all(void); int upgrade_callback_delete_all(void);
int upgrade_callback_call(clicon_handle h, cxobj *xt, char *namespace, uint32_t from, uint32_t to, cbuf *cbret); int upgrade_callback_call(clicon_handle h, cxobj *xt, char *namespace, uint32_t from, uint32_t to, cbuf *cbret);

View file

@ -541,6 +541,7 @@ rpc_callback_call(clicon_handle h,
typedef struct { typedef struct {
qelem_t uc_qelem; /* List header */ qelem_t uc_qelem; /* List header */
clicon_upgrade_cb uc_callback; /* RPC Callback */ clicon_upgrade_cb uc_callback; /* RPC Callback */
const char *uc_fnstr; /* Stringified fn name for debug */
void *uc_arg; /* Application specific argument to cb */ void *uc_arg; /* Application specific argument to cb */
char *uc_namespace; /* Module namespace */ char *uc_namespace; /* Module namespace */
uint32_t uc_rev; /* Module revision (to) in YYYYMMDD format or 0 */ uint32_t uc_rev; /* Module revision (to) in YYYYMMDD format or 0 */
@ -555,6 +556,7 @@ static upgrade_callback_t *upgrade_cb_list = NULL;
* *
* @param[in] h clicon handle * @param[in] h clicon handle
* @param[in] cb Callback called * @param[in] cb Callback called
* @param[in] fnstr Stringified function for debug
* @param[in] arg Domain-specific argument to send to callback * @param[in] arg Domain-specific argument to send to callback
* @param[in] namespace Module namespace (if NULL all modules) * @param[in] namespace Module namespace (if NULL all modules)
* @param[in] rev To module revision (0 means module obsoleted) * @param[in] rev To module revision (0 means module obsoleted)
@ -564,12 +566,13 @@ static upgrade_callback_t *upgrade_cb_list = NULL;
* @see upgrade_callback_call which makes the actual callback * @see upgrade_callback_call which makes the actual callback
*/ */
int int
upgrade_callback_register(clicon_handle h, upgrade_callback_reg_fn(clicon_handle h,
clicon_upgrade_cb cb, clicon_upgrade_cb cb,
char *namespace, const char *fnstr,
uint32_t revision, char *namespace,
uint32_t from, uint32_t revision,
void *arg) uint32_t from,
void *arg)
{ {
upgrade_callback_t *uc; upgrade_callback_t *uc;
@ -579,6 +582,7 @@ upgrade_callback_register(clicon_handle h,
} }
memset(uc, 0, sizeof(*uc)); memset(uc, 0, sizeof(*uc));
uc->uc_callback = cb; uc->uc_callback = cb;
uc->uc_fnstr = fnstr;
uc->uc_arg = arg; uc->uc_arg = arg;
if (namespace) if (namespace)
uc->uc_namespace = strdup(namespace); uc->uc_namespace = strdup(namespace);
@ -623,7 +627,7 @@ upgrade_callback_delete_all(void)
* @retval -1 Error * @retval -1 Error
* @retval 0 Invalid - cbret contains reason as netconf * @retval 0 Invalid - cbret contains reason as netconf
* @retval 1 OK * @retval 1 OK
* @see upgrade_callback_register which registers the callbacks * @see upgrade_callback_reg_fn which registers the callbacks
*/ */
int int
upgrade_callback_call(clicon_handle h, upgrade_callback_call(clicon_handle h,
@ -657,8 +661,14 @@ upgrade_callback_call(clicon_handle h,
clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_namespace); clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_namespace);
goto done; goto done;
} }
if (ret == 0) if (ret == 0){
if (cbuf_len(cbret)==0){
clicon_err(OE_CFG, 0, "Validation fail %s(%s): cbret not set",
uc->uc_fnstr, namespace);
goto done;
}
goto fail; goto fail;
}
nr++; nr++;
} }
uc = NEXTQ(upgrade_callback_t *, uc); uc = NEXTQ(upgrade_callback_t *, uc);

View file

@ -843,7 +843,7 @@ xml_addsub(cxobj *xp,
* @param[in] tag Name of new xml child * @param[in] tag Name of new xml child
* @retval xc Return the new child (xc) * @retval xc Return the new child (xc)
* @see xml_addsub * @see xml_addsub
* The name of the function is somewhat misleading * The name of the function is somewhat misleading, should be called "wrap"
*/ */
cxobj * cxobj *
xml_insert(cxobj *xp, xml_insert(cxobj *xp,

View file

@ -71,10 +71,146 @@
#include "clixon_xpath_ctx.h" #include "clixon_xpath_ctx.h"
#include "clixon_xpath.h" #include "clixon_xpath.h"
static int
changelog_rename(clicon_handle h,
cxobj *xt,
cxobj *xw,
char *tag)
{
int retval = -1;
xp_ctx *xctx = NULL;
char *str = NULL;
if (tag == NULL){
clicon_err(OE_XML, 0, "tag required");
goto done;
}
if (xpath_vec_ctx(xw, tag, &xctx) < 0)
goto done;
if (ctx2string(xctx, &str) < 0)
goto done;
if (!strlen(str)){
clicon_err(OE_XML, 0, "invalid rename tag: \"%s\"", str);
goto done;
}
if (xml_name_set(xw, str) < 0)
goto done;
// ok:
retval = 1;
done:
if (xctx)
ctx_free(xctx);
if (str)
free(str);
return retval;
// fail:
retval = 0;
goto done;
}
/* replace target XML */
static int
changelog_replace(clicon_handle h,
cxobj *xt,
cxobj *xw,
cxobj *xnew)
{
int retval = -1;
cxobj *x;
/* create a new node by parsing fttransform string and insert it at
target */
if (xnew == NULL){
clicon_err(OE_XML, 0, "new required");
goto done;
}
/* replace: remove all children of target */
while ((x = xml_child_i(xw, 0)) != NULL)
if (xml_purge(x) < 0)
goto done;
/* replace: first single node under <new> */
if (xml_child_nr(xnew) != 1){
clicon_err(OE_XML, 0, "Single child to <new> required");
goto done;
}
x = xml_child_i(xnew, 0);
/* Copy from xnew to (now) empty target */
if (xml_copy(x, xw) < 0)
goto done;
retval = 1;
done:
return retval;
}
/* create a new node by parsing "new" and insert it at
target */
static int
changelog_insert(clicon_handle h,
cxobj *xt,
cxobj *xw,
cxobj *xnew)
{
int retval = -1;
cxobj *x;
if (xnew == NULL){
clicon_err(OE_XML, 0, "new required");
goto done;
}
/* replace: add all new children to target */
while ((x = xml_child_i(xnew, 0)) != NULL)
if (xml_addsub(xw, x) < 0)
goto done;
// ok:
retval = 1;
done:
return retval;
// fail:
retval = 0;
goto done;
}
/* delete target */
static int
changelog_delete(clicon_handle h,
cxobj *xt,
cxobj *xw)
{
int retval = -1;
if (xml_purge(xw) < 0)
goto done;
retval = 1;
done:
return retval;
}
/* Move target node to location */
static int
changelog_move(clicon_handle h,
cxobj *xt,
cxobj *xw,
char *dst)
{
int retval = -1;
cxobj *xp; /* destination parent node */
if ((xp = xpath_first(xt, "%s", dst)) == NULL){
clicon_err(OE_XML, 0, "path required");
goto done;
}
if (xml_addsub(xp, xw) < 0)
goto done;
retval = 1;
done:
return retval;
}
/*! Perform a changelog operation /*! Perform a changelog operation
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xt XML to upgrade
* @param[in] xi Changelog item * @param[in] xi Changelog item
* @param[in] xn XML to upgrade
* @note XXX error handling! * @note XXX error handling!
* @note XXX xn --> xt xpath may not match * @note XXX xn --> xt xpath may not match
*/ */
@ -84,72 +220,84 @@ changelog_op(clicon_handle h,
cxobj *xi) cxobj *xi)
{ {
int retval = -1; int retval = -1;
char *op; char *op;
char *xptarget; /* xpath to target-node */ char *whenxpath; /* xpath to when */
char *xplocation; /* xpath to location-node (move) */ char *tag; /* xpath to extra path (move) */
char *ftransform; /* transform string format (modify, create) */ char *dst; /* xpath to extra path (move) */
cxobj *xtrg; /* xml target node */ cxobj *xnew; /* new xml (insert, replace) */
cxobj *xloc; /* xml location node */ char *wxpath; /* xpath to where (target-node) */
cxobj *xnew = NULL; cxobj **wvec = NULL; /* Vector of where(target) nodes */
cxobj *x; size_t wlen;
cxobj *xw;
int ret;
xp_ctx *xctx = NULL;
int i;
if ((op = xml_find_body(xi, "change-operation")) == NULL) if ((op = xml_find_body(xi, "op")) == NULL)
goto ok; goto ok;
if ((xptarget = xml_find_body(xi, "target-node")) == NULL) /* get common variables that may be used in the operations below */
tag = xml_find_body(xi, "tag");
dst = xml_find_body(xi, "dst");
xnew = xml_find(xi, "new");
whenxpath = xml_find_body(xi, "when");
if ((wxpath = xml_find_body(xi, "where")) == NULL)
goto ok; goto ok;
/* target node (if any) */ /* Get vector of target nodes meeting the where requirement */
if ((xtrg = xpath_first(xt, "%s", xptarget)) == NULL) if (xpath_vec(xt, "%s", &wvec, &wlen, wxpath) < 0)
goto fail; goto done;
// fprintf(stderr, "%s %s %s\n", __FUNCTION__, op, xml_name(xt)); for (i=0; i<wlen; i++){
xplocation = xml_find_body(xi, "location-node"); xw = wvec[i];
ftransform = xml_find_body(xi, "transform"); /* If 'when' exists and is false, skip this target */
if (strcmp(op, "insert") == 0){ if (whenxpath){
/* create a new node by parsing fttransform string and insert it at if (xpath_vec_ctx(xw, whenxpath, &xctx) < 0)
target */ goto done;
if (ftransform == NULL) if ((ret = ctx2boolean(xctx)) < 0)
goto fail; goto done;
if (xml_parse_va(&xtrg, NULL, "%s", ftransform) < 0) if (xctx){
goto done; ctx_free(xctx);
} xctx = NULL;
else if (strcmp(op, "delete") == 0){ }
/* delete target */ if (ret == 0)
if (xml_purge(xtrg) < 0) continue;
goto done; }
} /* Now switch on operation */
else if (strcmp(op, "move") == 0){ if (strcmp(op, "rename") == 0){
/* Move target node to location */ ret = changelog_rename(h, xt, xw, tag);
if ((xloc = xpath_first(xt, "%s", xplocation)) == NULL) }
goto fail; else if (strcmp(op, "replace") == 0){
if (xml_addsub(xloc, xtrg) < 0) ret = changelog_replace(h, xt, xw, xnew);
goto done; }
} else if (strcmp(op, "insert") == 0){
else if (strcmp(op, "replace") == 0){ ret = changelog_insert(h, xt, xw, xnew);
/* create a new node by parsing fttransform string and insert it at }
target */ else if (strcmp(op, "delete") == 0){
if (ftransform == NULL) ret = changelog_delete(h, xt, xw);
goto fail; }
/* replace: remove all children of target */ else if (strcmp(op, "move") == 0){
while ((x = xml_child_i(xtrg, 0)) != NULL) ret = changelog_move(h, xt, xw, dst);
if (xml_purge(x) < 0) }
goto done; else{
/* Parse the new node */ clicon_err(OE_XML, 0, "Unknown operation: %s", op);
if (xml_parse_va(&xnew, NULL, "%s", ftransform) < 0) goto done;
goto done; }
if (xml_rootchild(xnew, 0, &xnew) < 0) if (ret < 0)
goto done; goto done;
/* Copy old to new */ if (ret == 0)
if (xml_copy(xnew, xtrg) < 0) goto fail;
goto done; }
if (xml_purge(xnew) < 0)
goto done;
}
ok: ok:
retval = 1; retval = 1;
done: done:
if (wvec)
free(wvec);
if (xctx)
ctx_free(xctx);
return retval; return retval;
fail: fail:
retval = 0; retval = 0;
clicon_debug(1, "%s fail op:%s ", __FUNCTION__, op);
goto done; goto done;
} }
@ -170,7 +318,7 @@ changelog_iterate(clicon_handle h,
int ret; int ret;
int i; int i;
if (xpath_vec(xch, "change-log", &vec, &veclen) < 0) if (xpath_vec(xch, "step", &vec, &veclen) < 0)
goto done; goto done;
/* Iterate through changelog items */ /* Iterate through changelog items */
for (i=0; i<veclen; i++){ for (i=0; i<veclen; i++){
@ -181,6 +329,7 @@ changelog_iterate(clicon_handle h,
} }
retval = 1; retval = 1;
done: done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (vec) if (vec)
free(vec); free(vec);
return retval; return retval;
@ -233,7 +382,7 @@ xml_changelog_upgrade(clicon_handle h,
* - find all changelogs in the interval: [from, to] * - find all changelogs in the interval: [from, to]
* - note it t=0 then no changelog is applied * - note it t=0 then no changelog is applied
*/ */
if (xpath_vec(xchlog, "module[namespace=\"%s\"]", if (xpath_vec(xchlog, "changelog[namespace=\"%s\"]",
&vec, &veclen, namespace) < 0) &vec, &veclen, namespace) < 0)
goto done; goto done;
/* Get all changelogs in the interval [from,to]*/ /* Get all changelogs in the interval [from,to]*/

View file

@ -830,7 +830,6 @@ xp_union(xp_ctx *xc1,
return retval; return retval;
} }
/*! Evaluate an XPATH on an XML tree /*! Evaluate an XPATH on an XML tree
* The initial sequence of steps selects a set of nodes relative to a context node. * The initial sequence of steps selects a set of nodes relative to a context node.
@ -1038,11 +1037,20 @@ xp_eval(xp_ctx *xc,
} }
/*! Given XML tree and xpath, returns xpath context /*! Given XML tree and xpath, returns xpath context
* This is a raw form of xpath where you can do type conversion, etc,
* not just a nodeset.
* @param[in] xcur XML-tree where to search * @param[in] xcur XML-tree where to search
* @param[in] xpath String with XPATH 1.0 syntax * @param[in] xpath String with XPATH 1.0 syntax
* @param[out] xrp Return XPATH context * @param[out] xrp Return XPATH context
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @code
* xp_ctx *xc = NULL;
* if (xpath_vec_ctx(x, xpath, &xc) < 0)
* err;
* if (xc)
* ctx_free(xc);
* @endcode
*/ */
int int
xpath_vec_ctx(cxobj *xcur, xpath_vec_ctx(cxobj *xcur,
@ -1295,6 +1303,7 @@ xpath_vec_flag(cxobj *xcur,
} }
/*! Given XML tree and xpath, returns boolean /*! Given XML tree and xpath, returns boolean
* Returns true if the nodeset is non-empty
* @param[in] xcur xml-tree where to search * @param[in] xcur xml-tree where to search
* @param[in] xpath stdarg string with XPATH 1.0 syntax * @param[in] xpath stdarg string with XPATH 1.0 syntax
* @retval 1 True * @retval 1 True

View file

@ -40,6 +40,10 @@ module example-a{
type string; type string;
description "no change"; description "no change";
} }
leaf b {
type string;
description "rename tag";
}
leaf x { leaf x {
type string; type string;
description "delete"; description "delete";
@ -80,6 +84,10 @@ module example-a {
type string; type string;
description "no change"; description "no change";
} }
leaf c {
type string;
description "rename tag";
}
leaf host-name { leaf host-name {
type string; type string;
description "replace"; description "replace";
@ -144,6 +152,7 @@ cat <<EOF > $dir/startup_db
</modules-state> </modules-state>
<system xmlns="urn:example:a"> <system xmlns="urn:example:a">
<a>dont change me</a> <a>dont change me</a>
<b>rename me</b>
<host-name>modify me</host-name> <host-name>modify me</host-name>
<x>remove me</x> <x>remove me</x>
<z>move me</z> <z>move me</z>
@ -157,7 +166,7 @@ cat <<EOF > $dir/startup_db
EOF EOF
# Wanted new XML # Wanted new XML
XML='<system xmlns="urn:example:a"><a>dont change me</a><host-name>i am modified</host-name><y>created</y></system><alt xmlns="urn:example:a"><z>move me</z></alt>' XML='<system xmlns="urn:example:a"><a>dont change me</a><c>rename me</c><host-name>i am modified</host-name><y>created</y></system><alt xmlns="urn:example:a"><z>move me</z></alt>'
# Create configuration # Create configuration
@ -182,46 +191,52 @@ EOF
# Changelog of example-a: # Changelog of example-a:
cat <<EOF > $changelog cat <<EOF > $changelog
<yang-modules xmlns="http://clicon.org/xml-changelog"> <changelogs xmlns="http://clicon.org/xml-changelog">
<module> <changelog>
<namespace>urn:example:b</namespace> <namespace>urn:example:b</namespace>
<revfrom>2017-12-01</revfrom> <revfrom>2017-12-01</revfrom>
<revision>2017-12-20</revision> <revision>2017-12-20</revision>
<change-log> <step>
<index>0001</index> <name>1</name>
<change-operation>delete</change-operation> <op>delete</op>
<target-node>/b:system-b</target-node> <where>/b:system-b</where>
</change-log> </step>
</module> </changelog>
<module> <changelog>
<namespace>urn:example:a</namespace> <namespace>urn:example:a</namespace>
<revfrom>2017-12-01</revfrom> <revfrom>2017-12-01</revfrom>
<revision>2017-12-20</revision> <revision>2017-12-20</revision>
<change-log> <step>
<index>0001</index> <name>0</name>
<change-operation>insert</change-operation> <op>rename</op>
<target-node>/a:system</target-node> <where>/a:system/a:b</where>
<transform>&lt;y&gt;created&lt;/y&gt;</transform> <tag>"c"</tag>
</change-log> </step>
<change-log> <step>
<index>0002</index> <name>1</name>
<change-operation>delete</change-operation> <op>insert</op>
<target-node>/a:system/a:x</target-node> <where>/a:system</where>
</change-log> <new><y>created</y></new>
<change-log> </step>
<index>0003</index> <step>
<change-operation>replace</change-operation> <name>2</name>
<target-node>/a:system/a:host-name</target-node> <op>delete</op>
<transform>&lt;host-name&gt;i am modified&lt;/host-name&gt;</transform> <where>/a:system/a:x</where>
</change-log> </step>
<change-log> <step>
<index>0004</index> <name>3</name>
<change-operation>move</change-operation> <op>replace</op>
<target-node>/a:system/a:z</target-node> <where>/a:system/a:host-name</where>
<location-node>/a:alt</location-node> <new><host-name>i am modified</host-name></new>
</change-log> </step>
</module> <step>
</yang-modules> <name>4</name>
<op>move</op>
<where>/a:system/a:z</where>
<dst>/a:alt</dst>
</step>
</changelog>
</changelogs>
EOF EOF
# Start new system from old datastore # Start new system from old datastore

View file

@ -37,34 +37,37 @@ module clixon-xml-changelog {
More inspiration in XProc: https://www.w3.org/TR/xproc/#ex2"; More inspiration in XProc: https://www.w3.org/TR/xproc/#ex2";
type enumeration{ type enumeration{
enum rename { enum rename {
description "Rename the target node (NYI)"; description
"Rename the 'where' node, ie XML label
Synopsis: rename(where:targets, when:bool, tag:string)";
} }
enum replace { enum replace {
description "Replace the target data node description
modification is given by the leaf transform which "Replace the target data node modification is given by the leaf
is a string with %s where the original value transform which is a string with %s where the original value
is inserted"; is inserted.
Synopsis: replace(where:targets, when:bool, new:xml)";
} }
enum insert { enum insert {
description "Create new data nodes and insert under an existing node"; description
"Create new data nodes and insert under an existing node.
Synopsis: insert(where:parents, when:bool, new:xml)";
} }
enum delete { enum delete {
description "Delete the target node"; description
"Delete the target node.
Synopsis: delete(where:parents, when:bool)";
} }
enum move { enum move {
description "Move the target node(Added)"; description
} "Move the target node(Added).
enum wrap { Synopsis: move(where:parents, when:bool, dst:node)";
description "Wraps elements with additional elements(NYI)";
}
enum reorder {
description "Changes the order of elements (NYI)";
} }
} }
} }
container yang-modules { container changelogs {
config false; config false;
list module { list changelog {
key "namespace revision"; key "namespace revision";
leaf namespace { leaf namespace {
type string; type string;
@ -90,22 +93,22 @@ module clixon-xml-changelog {
Several changelogs may be applied if the upgrade spans multiple Several changelogs may be applied if the upgrade spans multiple
ranges: [from0,to0],..[fromN,toN]"; ranges: [from0,to0],..[fromN,toN]";
} }
list change-log { list step {
description description
"List for module revision change log"; "List for module revision change log";
key "index"; key "name";
leaf index { leaf name {
type uint32; type string;
description description
"Index for module change log"; "Unique step name";
} }
leaf change-operation { leaf op {
type operation_type; type operation_type;
mandatory true; mandatory true;
description description
"This leaf indicate the change operation, such as create, move, delete, modify, etc."; "This leaf indicate the change operation, such as create, move, delete, modify, etc.";
} }
leaf target-node { leaf where {
type yang:xpath1.0; type yang:xpath1.0;
mandatory true; mandatory true;
description description
@ -115,19 +118,27 @@ module clixon-xml-changelog {
For create, it is the parent where it should be For create, it is the parent where it should be
inserted."; inserted.";
} }
leaf location-node { leaf when {
type yang:xpath1.0;
description description
"If op is move, this denotes the destination"; "Boolean XPATH. Execute this step if this xpath exists
and evaluates to true";
}
leaf tag {
description
"For rename, a string XPath definining the new tag.";
type yang:xpath1.0; type yang:xpath1.0;
} }
leaf transform { leaf dst {
description description
"If op is modify or create, this denotes how to "For move, a destination XPath definining the parent where
transform the XML encoding. to insert.";
Special value %s for the original value."; type yang:xpath1.0;
type string; }
anydata new {
description
"If op is replace or insert, new XML for the new node.";
} }
} }
} }
} }