From 8624be0a6727741e68ff4068d9e91dcda4099574 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 26 Mar 2019 12:04:51 +0100 Subject: [PATCH] xml changelog next iteration --- apps/backend/backend_commit.c | 365 +++++++++--------- example/example_backend.c | 7 +- lib/clixon/clixon_plugin.h | 7 +- lib/src/clixon_plugin.c | 26 +- lib/src/clixon_xml.c | 2 +- lib/src/clixon_xml_changelog.c | 269 ++++++++++--- lib/src/clixon_xpath.c | 11 +- test/test_upgrade_changelog.sh | 85 ++-- .../clixon-xml-changelog@2019-03-21.yang | 73 ++-- 9 files changed, 512 insertions(+), 333 deletions(-) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index b968a8b0f..74c513608 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -140,116 +140,9 @@ generic_validate(yang_spec *yspec, goto done; } -/*! Validate a candidate db and comnpare to running - * Get both source and dest datastore, validate target, compute diffs - * and call application callback validations. - * @param[in] h Clicon handle - * @param[in] candidate The candidate database. The wanted backend state - * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) - * @retval 1 Validation OK - * @note Need to differentiate between error and validation fail - * (only done for generic_validate) - */ -static int -validate_common(clicon_handle h, - char *candidate, - transaction_data_t *td, - cbuf *cbret) -{ - int retval = -1; - yang_spec *yspec; - int i; - cxobj *xn; - int ret; - - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_FATAL, 0, "No DB_SPEC"); - goto done; - } - - /* This is the state we are going to */ - if (xmldb_get(h, candidate, "/", 1, &td->td_target, NULL) < 0) - goto done; - - /* Validate the target state. It is not completely clear this should be done - * here. It is being made in generic_validate below. - * But xml_diff requires some basic validation, at least check that yang-specs - * have been assigned - */ - if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) - goto done; - if (ret == 0) - 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 */ - if (xml_diff(yspec, - td->td_src, - td->td_target, - &td->td_dvec, /* removed: only in running */ - &td->td_dlen, - &td->td_avec, /* added: only in candidate */ - &td->td_alen, - &td->td_scvec, /* changed: original values */ - &td->td_tcvec, /* changed: wanted values */ - &td->td_clen) < 0) - goto done; - if (debug>1) - transaction_print(stderr, td); - /* Mark as changed in tree */ - for (i=0; itd_dlen; i++){ /* Also down */ - xn = td->td_dvec[i]; - xml_flag_set(xn, XML_FLAG_DEL); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - } - for (i=0; itd_alen; i++){ /* Also down */ - xn = td->td_avec[i]; - xml_flag_set(xn, XML_FLAG_ADD); - xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - } - for (i=0; itd_clen; i++){ /* Also up */ - xn = td->td_scvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - xn = td->td_tcvec[i]; - xml_flag_set(xn, XML_FLAG_CHANGE); - xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); - } - /* 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; - - /* 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: - return retval; - fail: - retval = 0; - goto done; -} - -/*! Validate a candidate db and comnpare to running XXX Experimental - * Get both source and dest datastore, validate target, compute diffs +/*! 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 @@ -259,6 +152,7 @@ validate_common(clicon_handle h, * @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 @@ -267,36 +161,32 @@ validate_common(clicon_handle h, * 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) +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; - 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_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 ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; + 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; @@ -329,7 +219,43 @@ startup_validate(clicon_handle h, /* 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; @@ -338,82 +264,37 @@ startup_validate(clicon_handle h, 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; } +/*! 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; - 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) + if ((ret = startup_common(h, db, 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; - + goto fail; /* 8. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) goto done; @@ -441,14 +322,116 @@ startup_commit(clicon_handle h, 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"); + retval = 0; + goto done; +} + +/*! Validate a candidate db and comnpare to running + * Get both source and dest datastore, validate target, compute diffs + * and call application callback validations. + * @param[in] h Clicon handle + * @param[in] candidate The candidate database. The wanted backend state + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK + * @note Need to differentiate between error and validation fail + * (only done for generic_validate) + */ +static int +from_validate_common(clicon_handle h, + char *candidate, + transaction_data_t *td, + cbuf *cbret) +{ + int retval = -1; + yang_spec *yspec; + int i; + cxobj *xn; + int ret; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; + } + + /* This is the state we are going to */ + if (xmldb_get(h, candidate, "/", 1, &td->td_target, NULL) < 0) + goto done; + + /* Validate the target state. It is not completely clear this should be done + * here. It is being made in generic_validate below. + * But xml_diff requires some basic validation, at least check that yang-specs + * have been assigned + */ + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) + goto done; + if (ret == 0) + 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 */ + if (xml_diff(yspec, + td->td_src, + td->td_target, + &td->td_dvec, /* removed: only in running */ + &td->td_dlen, + &td->td_avec, /* added: only in candidate */ + &td->td_alen, + &td->td_scvec, /* changed: original values */ + &td->td_tcvec, /* changed: wanted values */ + &td->td_clen) < 0) + goto done; + if (debug>1) + transaction_print(stderr, td); + /* Mark as changed in tree */ + for (i=0; itd_dlen; i++){ /* Also down */ + xn = td->td_dvec[i]; + xml_flag_set(xn, XML_FLAG_DEL); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + } + for (i=0; itd_alen; i++){ /* Also down */ + xn = td->td_avec[i]; + xml_flag_set(xn, XML_FLAG_ADD); + xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); } + for (i=0; itd_clen; i++){ /* Also up */ + xn = td->td_scvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + xn = td->td_tcvec[i]; + xml_flag_set(xn, XML_FLAG_CHANGE); + xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + } + /* 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; + + /* 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: + return retval; + fail: retval = 0; goto done; } @@ -482,7 +465,7 @@ candidate_commit(clicon_handle h, /* Common steps (with validate). Load candidate and running and compute diffs * 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; if (ret == 0) goto fail; @@ -697,7 +680,7 @@ from_client_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* 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); if (ret < 0){ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) diff --git a/example/example_backend.c b/example/example_backend.c index 7e037bb70..94b3dd37f 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -249,8 +249,7 @@ example_statedata(clicon_handle h, /*! Registered Upgrade callback function * @param[in] h Clicon handle * @param[in] xn XML tree to be updated - * @param[in] modname Name of module - * @param[in] modns Namespace of module (for info) + * @param[in] ns Namespace of module (for info) * @param[in] from From revision on the form YYYYMMDD * @param[in] to To revision on the form YYYYMMDD (0 not in system) * @param[in] arg User argument given at rpc_callback_register() @@ -466,11 +465,9 @@ clixon_plugin_init(clicon_handle h) ) < 0) goto done; /* General purpose upgrade callback */ - if (upgrade_callback_register(h, 0?upgrade_all:xml_changelog_upgrade, - NULL, 0, 0, NULL) < 0) + if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 0) goto done; - /* Return plugin API */ return &api; done: diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 37ee3cd31..1ee8857d0 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -201,6 +201,11 @@ struct clixon_plugin_api{ #define ca_trans_end u.cau_backend.cb_trans_end #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; /* 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); /* 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_call(clicon_handle h, cxobj *xt, char *namespace, uint32_t from, uint32_t to, cbuf *cbret); diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 4a29e06fe..8bb952d72 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -541,6 +541,7 @@ rpc_callback_call(clicon_handle h, typedef struct { qelem_t uc_qelem; /* List header */ clicon_upgrade_cb uc_callback; /* RPC Callback */ + const char *uc_fnstr; /* Stringified fn name for debug */ void *uc_arg; /* Application specific argument to cb */ char *uc_namespace; /* Module namespace */ 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] cb Callback called + * @param[in] fnstr Stringified function for debug * @param[in] arg Domain-specific argument to send to callback * @param[in] namespace Module namespace (if NULL all modules) * @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 */ int -upgrade_callback_register(clicon_handle h, - clicon_upgrade_cb cb, - char *namespace, - uint32_t revision, - uint32_t from, - void *arg) +upgrade_callback_reg_fn(clicon_handle h, + clicon_upgrade_cb cb, + const char *fnstr, + char *namespace, + uint32_t revision, + uint32_t from, + void *arg) { upgrade_callback_t *uc; @@ -579,6 +582,7 @@ upgrade_callback_register(clicon_handle h, } memset(uc, 0, sizeof(*uc)); uc->uc_callback = cb; + uc->uc_fnstr = fnstr; uc->uc_arg = arg; if (namespace) uc->uc_namespace = strdup(namespace); @@ -623,7 +627,7 @@ upgrade_callback_delete_all(void) * @retval -1 Error * @retval 0 Invalid - cbret contains reason as netconf * @retval 1 OK - * @see upgrade_callback_register which registers the callbacks + * @see upgrade_callback_reg_fn which registers the callbacks */ int 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); 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; + } nr++; } uc = NEXTQ(upgrade_callback_t *, uc); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 9cb9f4891..19602df72 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -843,7 +843,7 @@ xml_addsub(cxobj *xp, * @param[in] tag Name of new xml child * @retval xc Return the new child (xc) * @see xml_addsub - * The name of the function is somewhat misleading + * The name of the function is somewhat misleading, should be called "wrap" */ cxobj * xml_insert(cxobj *xp, diff --git a/lib/src/clixon_xml_changelog.c b/lib/src/clixon_xml_changelog.c index bc4336f8a..7fea95664 100644 --- a/lib/src/clixon_xml_changelog.c +++ b/lib/src/clixon_xml_changelog.c @@ -71,10 +71,146 @@ #include "clixon_xpath_ctx.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 */ + if (xml_child_nr(xnew) != 1){ + clicon_err(OE_XML, 0, "Single child to 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 * @param[in] h Clicon handle + * @param[in] xt XML to upgrade * @param[in] xi Changelog item - * @param[in] xn XML to upgrade + * @note XXX error handling! * @note XXX xn --> xt xpath may not match */ @@ -84,72 +220,84 @@ changelog_op(clicon_handle h, cxobj *xi) { - int retval = -1; - char *op; - char *xptarget; /* xpath to target-node */ - char *xplocation; /* xpath to location-node (move) */ - char *ftransform; /* transform string format (modify, create) */ - cxobj *xtrg; /* xml target node */ - cxobj *xloc; /* xml location node */ - cxobj *xnew = NULL; - cxobj *x; + int retval = -1; + char *op; + char *whenxpath; /* xpath to when */ + char *tag; /* xpath to extra path (move) */ + char *dst; /* xpath to extra path (move) */ + cxobj *xnew; /* new xml (insert, replace) */ + char *wxpath; /* xpath to where (target-node) */ + cxobj **wvec = NULL; /* Vector of where(target) nodes */ + 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; - 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; - /* target node (if any) */ - if ((xtrg = xpath_first(xt, "%s", xptarget)) == NULL) - goto fail; - // fprintf(stderr, "%s %s %s\n", __FUNCTION__, op, xml_name(xt)); - xplocation = xml_find_body(xi, "location-node"); - ftransform = xml_find_body(xi, "transform"); - if (strcmp(op, "insert") == 0){ - /* create a new node by parsing fttransform string and insert it at - target */ - if (ftransform == NULL) - goto fail; - if (xml_parse_va(&xtrg, NULL, "%s", ftransform) < 0) - goto done; - } - else if (strcmp(op, "delete") == 0){ - /* delete target */ - if (xml_purge(xtrg) < 0) - goto done; - } - else if (strcmp(op, "move") == 0){ - /* Move target node to location */ - if ((xloc = xpath_first(xt, "%s", xplocation)) == NULL) - goto fail; - if (xml_addsub(xloc, xtrg) < 0) - goto done; - } - else if (strcmp(op, "replace") == 0){ - /* create a new node by parsing fttransform string and insert it at - target */ - if (ftransform == NULL) - goto fail; - /* replace: remove all children of target */ - while ((x = xml_child_i(xtrg, 0)) != NULL) - if (xml_purge(x) < 0) - goto done; - /* Parse the new node */ - if (xml_parse_va(&xnew, NULL, "%s", ftransform) < 0) - goto done; - if (xml_rootchild(xnew, 0, &xnew) < 0) - goto done; - /* Copy old to new */ - if (xml_copy(xnew, xtrg) < 0) - goto done; - if (xml_purge(xnew) < 0) - goto done; - } + /* Get vector of target nodes meeting the where requirement */ + if (xpath_vec(xt, "%s", &wvec, &wlen, wxpath) < 0) + goto done; + for (i=0; i $dir/startup_db dont change me + rename me modify me remove me move me @@ -157,7 +166,7 @@ cat < $dir/startup_db EOF # Wanted new XML -XML='dont change mei am modifiedcreatedmove me' +XML='dont change merename mei am modifiedcreatedmove me' # Create configuration @@ -182,46 +191,52 @@ EOF # Changelog of example-a: cat < $changelog - - + + urn:example:b 2017-12-01 2017-12-20 - - 0001 - delete - /b:system-b - - - + + 1 + delete + /b:system-b + + + urn:example:a 2017-12-01 2017-12-20 - - 0001 - insert - /a:system - <y>created</y> - - - 0002 - delete - /a:system/a:x - - - 0003 - replace - /a:system/a:host-name - <host-name>i am modified</host-name> - - - 0004 - move - /a:system/a:z - /a:alt - - - + + 0 + rename + /a:system/a:b + "c" + + + 1 + insert + /a:system + created + + + 2 + delete + /a:system/a:x + + + 3 + replace + /a:system/a:host-name + i am modified + + + 4 + move + /a:system/a:z + /a:alt + + + EOF # Start new system from old datastore diff --git a/yang/clixon/clixon-xml-changelog@2019-03-21.yang b/yang/clixon/clixon-xml-changelog@2019-03-21.yang index b5130db6e..4533c2e7e 100644 --- a/yang/clixon/clixon-xml-changelog@2019-03-21.yang +++ b/yang/clixon/clixon-xml-changelog@2019-03-21.yang @@ -37,34 +37,37 @@ module clixon-xml-changelog { More inspiration in XProc: https://www.w3.org/TR/xproc/#ex2"; type enumeration{ 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 { - description "Replace the target data node - modification is given by the leaf transform which - is a string with %s where the original value - is inserted"; + description + "Replace the target data node modification is given by the leaf + transform which is a string with %s where the original value + is inserted. + Synopsis: replace(where:targets, when:bool, new:xml)"; } 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 { - description "Delete the target node"; + description + "Delete the target node. + Synopsis: delete(where:parents, when:bool)"; } enum move { - description "Move the target node(Added)"; - } - enum wrap { - description "Wraps elements with additional elements(NYI)"; - } - enum reorder { - description "Changes the order of elements (NYI)"; + description + "Move the target node(Added). + Synopsis: move(where:parents, when:bool, dst:node)"; } } } - container yang-modules { + container changelogs { config false; - list module { + list changelog { key "namespace revision"; leaf namespace { type string; @@ -90,22 +93,22 @@ module clixon-xml-changelog { Several changelogs may be applied if the upgrade spans multiple ranges: [from0,to0],..[fromN,toN]"; } - list change-log { + list step { description "List for module revision change log"; - key "index"; - leaf index { - type uint32; + key "name"; + leaf name { + type string; description - "Index for module change log"; + "Unique step name"; } - leaf change-operation { + leaf op { type operation_type; mandatory true; description "This leaf indicate the change operation, such as create, move, delete, modify, etc."; } - leaf target-node { + leaf where { type yang:xpath1.0; mandatory true; description @@ -115,19 +118,27 @@ module clixon-xml-changelog { For create, it is the parent where it should be inserted."; } - leaf location-node { + leaf when { + type yang:xpath1.0; + description + "Boolean XPATH. Execute this step if this xpath exists + and evaluates to true"; + } + leaf tag { description - "If op is move, this denotes the destination"; + "For rename, a string XPath definining the new tag."; type yang:xpath1.0; } - leaf transform { + leaf dst { description - "If op is modify or create, this denotes how to - transform the XML encoding. - Special value %s for the original value."; - type string; + "For move, a destination XPath definining the parent where + to insert."; + type yang:xpath1.0; + } + anydata new { + description + "If op is replace or insert, new XML for the new node."; } - } } }