diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2981e3..e51816e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,23 +16,25 @@ * Enable with CLICON_XMLDB_MODSTATE config option * Check modules-state tags when loading a datastore at startup * Check which modules match, and which do not. - * Loading of "extra" XML. + * Loading of "extra" XML, such as from a file. * Detection of in-compatible XML and Yang models in the startup configuration. * A user can register upgrade callbacks per module/revision when in-compatible XML is encountered (`update_callback_register`). + * See the [example](example/example_backend.c) and [test](test/test_upgrade_interfaces.sh]. * 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` -* New yang changelog experimental feature for automatic upgrade - * Yang module clixon-yang-changelog@2019-03-21.yang based on draft-wang-netmod-module-revision-management-01 - * Two config options control: - * CLICON_YANG_CHANGELOG enables the yang changelog feature - * CLICON_YANG_CHANGELOG_FILE where the changelog resides * Datastore files contain RFC7895 module-state information * Added modules-state diff 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 +* New xml changelog experimental feature for automatic upgrade + * Yang module clixon-xml-changelog@2019-03-21.yang based on draft-wang-netmod-module-revision-management-01 + * Two config options control: + * CLICON_XML_CHANGELOG enables the yang changelog feature + * CLICON_XML_CHANGELOG_FILE where the changelog resides ### API changes on existing features (you may need to change your code) +* Renamed `xml_insert` to `xml_wrap_all`. * Added modules-state diff parameter to xmldb_get datastore function for startup scenarios. Set this to NULL in normal cases. * `rpc_callback_register` added a namespace parameter. Example: ``` @@ -50,7 +52,10 @@ ``` ### Minor changes -* Added synatctic check for yang status: current, deprectated or obsolete. +* Added syntactic check for yang status: current, deprecated or obsolete. +* Added `xml_wrap` function that adds an XML node above a node as a wrapper + * also renamed `xml_insert` to `xml_wrap_all`. +* Added `clicon_argv_get()` function to get the user command-line options, ie the args in `-- `. This is an alternative to using them passed to `plugin_start()`. * Made Makefile concurrent so that it can be compiled with -jN * Added flags to example backend to control its behaviour: * Start with `-- -r` to run the reset plugin diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 581b3153..74c51360 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -140,6 +140,194 @@ generic_validate(yang_spec *yspec, 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 * Get both source and dest datastore, validate target, compute diffs * and call application callback validations. @@ -152,10 +340,10 @@ generic_validate(yang_spec *yspec, * (only done for generic_validate) */ static int -validate_common(clicon_handle h, - char *candidate, - transaction_data_t *td, - cbuf *cbret) +from_validate_common(clicon_handle h, + char *candidate, + transaction_data_t *td, + cbuf *cbret) { int retval = -1; yang_spec *yspec; @@ -248,94 +436,6 @@ validate_common(clicon_handle h, goto done; } -/*! Validate a candidate db and comnpare to running XXX Experimental - * Get both source and dest datastore, validate target, compute diffs - * and call application callback validations. - * @param[in] h Clicon handle - * @param[in] candidate The candidate database. The wanted backend state - * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) - * @retval 1 Validation OK - * @note Need to differentiate between error and validation fail - * 1. Parse startup XML (or JSON) - * 2. If syntax failure, call startup-cb(ERROR), copy failsafe db to - * candidate and commit. Done - * 3. Check yang module versions between backend and init config XML. (msdiff) - * 4. Validate startup db. (valid) - * 5. If valid fails, call startup-cb(Invalid, msdiff), keep startup in candidate and commit failsafe db. Done. - * 6. Call startup-cb(OK, msdiff) and commit. - */ -int -startup_validate(clicon_handle h, - char *db, - cbuf *cbret) -{ - int retval = -1; - yang_spec *yspec; - int ret; - modstate_diff_t *msd = 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, &td->td_target, msd) < 0) - goto done; - if ((ret = clixon_module_upgrade(h, td->td_target, msd, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; - - /* 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 (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 * * The code reverts changes if the commit fails. But if the revert @@ -365,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; @@ -580,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/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 0d6418b5..adcdc0bc 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -40,7 +40,8 @@ /* * Prototypes */ -int startup_validate(clicon_handle h, char *db, cbuf *cbret); +int startup_validate(clicon_handle h, char *db, cxobj **xtr, cbuf *cbret); +int startup_commit(clicon_handle h, char *db, cbuf *cbret); int candidate_commit(clicon_handle h, char *db, cbuf *cbret); int from_client_commit(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 6e08006d..16141a74 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -98,7 +98,7 @@ backend_terminate(clicon_handle h) close(ss); if ((x = clicon_module_state_get(h)) != NULL) xml_free(x); - if ((x = clicon_yang_changelog_get(h)) != NULL) + if ((x = clicon_xml_changelog_get(h)) != NULL) xml_free(x); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -509,6 +509,7 @@ main(int argc, argc -= optind; argv += optind; + clicon_argv_set(h, argv0, argc, argv); clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); /* Defer: Wait to the last minute to print help message */ @@ -645,8 +646,8 @@ main(int argc, goto done; /* Must be after netconf_module_load, but before startup code */ - if (clicon_option_bool(h, "CLICON_YANG_CHANGELOG")) - if (clixon_yang_changelog_init(h) < 0) + if (clicon_option_bool(h, "CLICON_XML_CHANGELOG")) + if (clixon_xml_changelog_init(h) < 0) goto done; /* Save modules state of the backend (server). Compare with startup XML */ @@ -705,6 +706,8 @@ main(int argc, } if (status != STARTUP_OK){ + if (cbuf_len(cbret)) + clicon_log(LOG_NOTICE, "%s: %u %s", __PROGRAM__, getpid(), cbuf_get(cbret)); if (startup_failsafe(h) < 0){ goto done; } diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 11780244..98697664 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -161,15 +161,10 @@ startup_mode_startup(clicon_handle h, if (xmldb_create(h, db) < 0) /* diff */ return -1; } - if ((ret = startup_validate(h, db, cbret)) < 0) + if ((ret = startup_commit(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; @@ -241,6 +236,7 @@ startup_extraxml(clicon_handle h, int retval = -1; char *db = "tmp"; int ret; + cxobj *xt = NULL; /* Potentially upgraded XML */ /* Clear tmp db */ if (startup_db_reset(h, db) < 0) @@ -256,11 +252,17 @@ startup_extraxml(clicon_handle h, if (ret == 0) goto fail; } - /* Validate tmp (unless empty?) */ - if ((ret = startup_validate(h, db, cbret)) < 0) + /* Validate the tmp db and return possibly upgraded xml in xt + */ + if ((ret = startup_validate(h, db, &xt, cbret)) < 0) goto done; if (ret == 0) goto fail; + /* Write (potentially modified) xml tree xt back to tmp + */ + if ((ret = xmldb_put(h, "tmp", OP_REPLACE, xt, + clicon_username_get(h), cbret)) < 0) + goto done; /* Merge tmp into running (no commit) */ if ((ret = db_merge(h, db, "running", cbret)) < 0) goto fail; @@ -268,6 +270,8 @@ startup_extraxml(clicon_handle h, goto fail; retval = 1; done: + if (xt) + xml_free(xt); if (xmldb_delete(h, "tmp") != 0 && errno != ENOENT) return -1; return retval; @@ -300,15 +304,16 @@ startup_failsafe(clicon_handle h) 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"); + clicon_err(OE_DB, 0, "Startup failed and no Failsafe database found, exiting"); 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)); + clicon_err(OE_DB, 0, "Startup failed, Failsafe database validation failed %s", cbuf_get(cbret)); goto done; } + clicon_log(LOG_NOTICE, "Startup failed, Failsafe database loaded "); retval = 0; done: if (cbret) diff --git a/example/example_backend.c b/example/example_backend.c index ec32e65c..6a8e329a 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -34,6 +34,8 @@ */ #include #include +#include +#include #include #include #include @@ -64,6 +66,12 @@ static int _reset = 0; */ static int _state = 0; +/*! Variable to control upgrade callbacks. + * If set, call test-case for upgrading ietf-interfaces, otherwise call + * auto-upgrade + */ +static int _upgrade = 0; + /* forward */ static int example_stream_timer_setup(clicon_handle h); @@ -246,12 +254,10 @@ example_statedata(clicon_handle h, return retval; } -#ifdef keep_as_example -/*! Registered Upgrade callback function +/*! Testcase upgrade function moving interfaces-state to interfaces * @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() @@ -259,22 +265,182 @@ example_statedata(clicon_handle h, * @retval 1 OK * @retval 0 Invalid * @retval -1 Error + * @see clicon_upgrade_cb + * @see test_upgrade_interfaces.sh + * @see upgrade_interfaces_2016 + * This example shows a two-step upgrade where the 2014 function does: + * - Move /if:interfaces-state/if:interface/if:admin-status to + * /if:interfaces/if:interface/ + * - Move /if:interfaces-state/if:interface/if:statistics to + * /if:interfaces/if:interface/ + * - Rename /interfaces/interface/description to descr */ static int -upgrade_all(clicon_handle h, - cxobj *xn, - char *modname, - char *modns, - uint32_t from, - uint32_t to, - void *arg, - cbuf *cbret) +upgrade_interfaces_2014(clicon_handle h, + cxobj *xt, + char *ns, + uint32_t from, + uint32_t to, + void *arg, + cbuf *cbret) { - fprintf(stderr, "%s XML:%s mod:%s %s from:%d to:%d\n", __FUNCTION__, xml_name(xn), - modname, modns, from, to); - return 1; + int retval = -1; + yang_spec *yspec; + yang_stmt *ym; + cxobj **vec = NULL; + cxobj *xc; + cxobj *xi; /* xml /interfaces-states/interface node */ + cxobj *x; + cxobj *xif; /* xml /interfaces/interface node */ + size_t vlen; + int i; + char *name; + + /* Get Yang module for this namespace. Note it may not exist (if obsolete) */ + yspec = clicon_dbspec_yang(h); + if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL) + goto ok; /* shouldnt happen */ + clicon_debug(1, "%s module %s", __FUNCTION__, ym?ym->ys_argument:"none"); + /* Get all XML nodes with that namespace */ + if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0) + goto done; + for (i=0; i..., ys_argument:"none"); + /* Get all XML nodes with that namespace */ + if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0) + goto done; + for (i=0; i" where is any choice of * options specific to the application. These options are passed to the * plugin_start function via the argc and argv arguments which @@ -343,22 +509,9 @@ example_reset(clicon_handle h, */ int example_start(clicon_handle h, - int argc, - char **argv) + int argc, + char **argv) { - char c; - - opterr = 0; - optind = 1; - while ((c = getopt(argc, argv, "rs")) != -1) - switch (c) { - case 'r': - _reset = 1; - break; - case 's': - _state = 1; - break; - } return 0; } @@ -389,13 +542,36 @@ static clixon_plugin_api api = { * @param[in] h Clixon handle * @retval NULL Error with clicon_err set * @retval api Pointer to API struct + * In this example, you can pass -r, -s, -u to control the behaviour, mainly + * for use in the test suites. */ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { struct timeval retention = {0,0}; + int argc; /* command-line options (after --) */ + char **argv; + char c; clicon_debug(1, "%s backend", __FUNCTION__); + + if (clicon_argv_get(h, &argc, &argv) < 0) + goto done; + opterr = 0; + optind = 1; + while ((c = getopt(argc, argv, "rsu")) != -1) + switch (c) { + case 'r': + _reset = 1; + break; + case 's': + _state = 1; + break; + case 'u': + _upgrade = 1; + break; + } + /* Example stream initialization: * 1) Register EXAMPLE stream * 2) setup timer for notifications, so something happens on stream @@ -444,14 +620,18 @@ clixon_plugin_init(clicon_handle h) "copy-config" ) < 0) goto done; - /* Called after the regular system copy_config callback */ - if (upgrade_callback_register(h, yang_changelog_upgrade, - NULL, - NULL, NULL, - 0, 0 - ) < 0) - goto done; - + /* Upgrade callback: if you start the backend with -- -u you will get the + * test interface example. Otherwise the auto-upgrade feature is enabled. + */ + if (_upgrade){ + if (upgrade_callback_register(h, upgrade_interfaces_2014, "urn:example:interfaces", 0, 0, NULL) < 0) + goto done; + if (upgrade_callback_register(h, upgrade_interfaces_2016, "urn:example:interfaces", 0, 0, NULL) < 0) + goto done; + } + else + if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 0) + goto done; /* Return plugin API */ return &api; diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index ec97916e..251cb72e 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -90,7 +90,7 @@ #include #include #include -#include +#include /* * Global variables generated by Makefile diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 629d6d80..8abf3b2a 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -210,8 +210,12 @@ int clicon_socket_set(clicon_handle h, int s); cxobj *clicon_module_state_get(clicon_handle h); int clicon_module_state_set(clicon_handle h, cxobj *xms); -/*! Set and get module revision changelog */ -cxobj *clicon_yang_changelog_get(clicon_handle h); -int clicon_yang_changelog_set(clicon_handle h, cxobj *xchlog); +/*! Set and get yang/xml module revision changelog */ +cxobj *clicon_xml_changelog_get(clicon_handle h); +int clicon_xml_changelog_set(clicon_handle h, cxobj *xchlog); + +/*! Set and get user command-line options (after --) */ +int clicon_argv_get(clicon_handle h, int *argc, char ***argv); +int clicon_argv_set(clicon_handle h, char *argv0, int argc, char **argv); #endif /* _CLIXON_OPTIONS_H_ */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 7b8fba66..5886cf58 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -70,8 +70,7 @@ typedef int (*clicon_rpc_cb)( /*! 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] namespace Namespace of module * @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() @@ -83,8 +82,7 @@ typedef int (*clicon_rpc_cb)( typedef int (*clicon_upgrade_cb)( clicon_handle h, cxobj *xn, - char *modname, - char *modns, + char *namespace, uint32_t from, uint32_t to, void *arg, @@ -203,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 @@ -246,8 +249,8 @@ int rpc_callback_delete_all(clicon_handle h); 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, void *arg, char *name, char *namespace, uint32_t from, uint32_t to); +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(clicon_handle h); -int upgrade_callback_call(clicon_handle h, cxobj *xt, char *modname, char *modns, 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); #endif /* _CLIXON_PLUGIN_H_ */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 69655697..4933115b 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -127,7 +127,9 @@ int xml_cv_set(cxobj *x, cg_var *cv); cxobj *xml_find(cxobj *xn_parent, char *name); int xml_addsub(cxobj *xp, cxobj *xc); -cxobj *xml_insert(cxobj *xt, char *tag); +cxobj *xml_wrap_all(cxobj *xp, char *tag); +cxobj *xml_wrap(cxobj *xc, char *tag); +#define xml_insert(x,t) xml_wrap_all((x),(t)) int xml_purge(cxobj *xc); int xml_child_rm(cxobj *xp, int i); int xml_rm(cxobj *xc); diff --git a/lib/clixon/clixon_yang_changelog.h b/lib/clixon/clixon_xml_changelog.h similarity index 80% rename from lib/clixon/clixon_yang_changelog.h rename to lib/clixon/clixon_xml_changelog.h index d8f5a65d..587478df 100644 --- a/lib/clixon/clixon_yang_changelog.h +++ b/lib/clixon/clixon_xml_changelog.h @@ -34,13 +34,14 @@ * YANG module revision change management. * See draft-wang-netmod-module-revision-management-01 */ -#ifndef _CLIXON_YANG_CHANGELOG_H -#define _CLIXON_YANG_CHANGELOG_H +#ifndef _CLIXON_XML_CHANGELOG_H +#define _CLIXON_XML_CHANGELOG_H /* * Prototypes */ -int yang_changelog_upgrade(clicon_handle h, cxobj *xn, char *modname, char *modns, uint32_t from, uint32_t to, void *arg, cbuf *cbret); -int clixon_yang_changelog_init(clicon_handle h); +int xml_changelog_upgrade(clicon_handle h, cxobj *xn, char *namespace, uint32_t from, uint32_t to, void *arg, cbuf *cbret); +int clixon_xml_changelog_init(clicon_handle h); +int xml_namespace_vec(clicon_handle h, cxobj *xt, char *namespace, cxobj ***vec, size_t *veclen); -#endif /* _CLIXON_YANG_CHANGELOG_H */ +#endif /* _CLIXON_XML_CHANGELOG_H */ diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 5466ecd7..160706b5 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -51,8 +51,8 @@ * This is in state of flux so it needss to be conatained and easily changed. */ typedef struct { - cxobj *md_del; /* yang mdoule state deletes */ - cxobj *md_mod; /* yang mdoule state modifications */ + cxobj *md_del; /* yang module state deletes */ + cxobj *md_mod; /* yang module state modifications */ } modstate_diff_t; /* diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index c80ec2cb..8e76ac2c 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -70,7 +70,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_string.c clixon_handle.c \ clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \ clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \ - clixon_yang_cardinality.c clixon_yang_changelog.c \ + clixon_yang_cardinality.c clixon_xml_changelog.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 6dc0f754..fdc87c89 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1043,8 +1043,8 @@ netconf_module_load(clicon_handle h) if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) goto done; /* YANG module revision change management */ - if (clicon_option_bool(h, "CLICON_YANG_CHANGELOG")) - if (yang_spec_parse_module(h, "clixon-yang-changelog", NULL, yspec)< 0) + if (clicon_option_bool(h, "CLICON_XML_CHANGELOG")) + if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0) goto done; retval = 0; done: diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 2bebe421..6862605f 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -1019,17 +1019,17 @@ clicon_module_state_set(clicon_handle h, * @see draft-wang-netmod-module-revision-management-01 */ cxobj * -clicon_yang_changelog_get(clicon_handle h) +clicon_xml_changelog_get(clicon_handle h) { clicon_hash_t *cdat = clicon_data(h); void *p; - if ((p = hash_value(cdat, "yang-changelog", NULL)) != NULL) + if ((p = hash_value(cdat, "xml-changelog", NULL)) != NULL) return *(cxobj **)p; return NULL; } -/*! Set yang module changelog +/*! Set xml module changelog * @param[in] h Clicon handle * @param[in] s Module revision changelog XML tree * @retval 0 OK @@ -1037,12 +1037,69 @@ clicon_yang_changelog_get(clicon_handle h) * @see draft-wang-netmod-module-revision-management-01 */ int -clicon_yang_changelog_set(clicon_handle h, +clicon_xml_changelog_set(clicon_handle h, cxobj *xchlog) { clicon_hash_t *cdat = clicon_data(h); - if (hash_add(cdat, "yang_changelog", &xchlog, sizeof(xchlog))==NULL) + if (hash_add(cdat, "xml-changelog", &xchlog, sizeof(xchlog))==NULL) + return -1; + return 0; +} + +/*! Get user clicon command-line options argv, argc (after --) + * @param[in] h Clicon handle + * @param[out] argc + * @param[out] argv + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_argv_get(clicon_handle h, + int *argc, + char ***argv) + +{ + clicon_hash_t *cdat = clicon_data(h); + void *p; + + if ((p = hash_value(cdat, "argc", NULL)) == NULL) + return -1; + *argc = *(int*)p; + if ((p = hash_value(cdat, "argv", NULL)) == NULL) + return -1; + *argv = *(char***)p; + return 0; +} + +/*! Set clicon user command-line options argv, argc (after --) + * @param[in] h Clicon handle + * @param[in] prog argv[0] - the program name + * @param[in] argc Length of argv + * @param[in] argv Array of command-line options + * @retval 0 OK + * @retval -1 Error + */ +int +clicon_argv_set(clicon_handle h, + char *prgm, + int argc, + char **argv) +{ + clicon_hash_t *cdat = clicon_data(h); + char **argvv = NULL; + + /* add space for null-termination and argv[0] program name */ + if ((argvv = calloc(argc+2, sizeof(char*))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + return -1; + } + memcpy(argvv+1, argv, argc*sizeof(char*)); + argvv[0] = prgm; + if (hash_add(cdat, "argv", &argvv, sizeof(argvv))==NULL) + return -1; + argc += 1; + if (hash_add(cdat, "argc", &argc, sizeof(argc))==NULL) return -1; return 0; } diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 41e1ea21..8f4bed82 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -539,13 +539,14 @@ rpc_callback_call(clicon_handle h, * revision. */ typedef struct { - qelem_t uc_qelem; /* List header */ + qelem_t uc_qelem; /* List header */ clicon_upgrade_cb uc_callback; /* RPC Callback */ - void *uc_arg; /* Application specific argument to cb */ - char *uc_name; /* Module name */ - char *uc_namespace; /* Module namespace ??? */ - uint32_t uc_from; /* Module revision (from) or 0 in YYYYMMDD format */ - uint32_t uc_to; /* Module revision (to) in YYYYMMDD format */ + 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 */ + uint32_t uc_from; /* Module revision (from) or 0 in YYYYMMDD format */ + } upgrade_callback_t; /* List of rpc callback entries XXX hang on handle */ @@ -555,23 +556,23 @@ 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] name Module name (if NULL all modules) - * @param[in] namespace Module namespace (NOTE not relevant) + * @param[in] namespace Module namespace (if NULL all modules) + * @param[in] rev To module revision (0 means module obsoleted) * @param[in] from From module revision (0 from any revision) - * @param[in] to To module revision (0 means module obsoleted) * @retval 0 OK * @retval -1 Error * @see upgrade_callback_call which makes the actual callback */ int -upgrade_callback_register(clicon_handle h, - clicon_upgrade_cb cb, - void *arg, - char *name, - char *namespace, - uint32_t from, - uint32_t to) +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; @@ -581,19 +582,16 @@ upgrade_callback_register(clicon_handle h, } memset(uc, 0, sizeof(*uc)); uc->uc_callback = cb; + uc->uc_fnstr = fnstr; uc->uc_arg = arg; - if (name) - uc->uc_name = strdup(name); if (namespace) uc->uc_namespace = strdup(namespace); + uc->uc_rev = revision; uc->uc_from = from; - uc->uc_to = to; ADDQ(uc, upgrade_cb_list); return 0; done: if (uc){ - if (uc->uc_name) - free(uc->uc_name); if (uc->uc_namespace) free(uc->uc_namespace); free(uc); @@ -610,8 +608,6 @@ upgrade_callback_delete_all(clicon_handle h) while((uc = upgrade_cb_list) != NULL) { DELQ(uc, upgrade_cb_list, upgrade_callback_t *); - if (uc->uc_name) - free(uc->uc_name); if (uc->uc_namespace) free(uc->uc_namespace); free(uc); @@ -622,7 +618,7 @@ upgrade_callback_delete_all(clicon_handle h) /*! Search Upgrade callbacks and invoke if module match * * @param[in] h clicon handle - * @param[in] xt XML tree to be updated + * @param[in] xt Top-level XML tree to be updated (includes other ns as well) * @param[in] modname Name of module * @param[in] modns Namespace of module (for info) * @param[in] from From revision on the form YYYYMMDD @@ -631,13 +627,12 @@ upgrade_callback_delete_all(clicon_handle h) * @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, cxobj *xt, - char *modname, - char *modns, + char *namespace, uint32_t from, uint32_t to, cbuf *cbret) @@ -659,15 +654,21 @@ upgrade_callback_call(clicon_handle h, * - Registered from revision >= from AND * - Registered to revision <= to (which includes case both 0) */ - if (uc->uc_name == NULL || strcmp(uc->uc_name, modname)==0) + if (uc->uc_namespace == NULL || strcmp(uc->uc_namespace, namespace)==0) if ((uc->uc_from == 0) || - (uc->uc_from >= from && uc->uc_to <= to)){ - if ((ret = uc->uc_callback(h, xt, modname, modns, from, to, uc->uc_arg, cbret)) < 0){ - clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_name); + (uc->uc_from >= from && uc->uc_rev <= to)){ + if ((ret = uc->uc_callback(h, xt, namespace, from, to, uc->uc_arg, cbret)) < 0){ + 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 7b346887..c4d561ad 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -560,7 +560,8 @@ xml_child_nr_type(cxobj *xn, /*! Get a specific child * @param[in] xn xml node * @param[in] i the number of the child, eg order in children vector - * @retval child in XML tree, or NULL if no such child, or empty child + * @retval xml The child xml node + * @retval NULL if no such child, or empty child */ cxobj * xml_child_i(cxobj *xn, @@ -633,6 +634,8 @@ xml_child_each(cxobj *xparent, int i; cxobj *xn = NULL; + if (xparent == NULL) + return NULL; for (i=xprev?xprev->_x_vector_i+1:0; ix_childvec_len; i++){ xn = xparent->x_childvec[i]; if (xn == NULL) @@ -805,7 +808,7 @@ xml_find(cxobj *x_up, * @param[in] xc Child xml node to insert under xp * @retval 0 OK * @retval -1 Error - * @see xml_insert + * @see xml_wrap */ int xml_addsub(cxobj *xp, @@ -833,30 +836,55 @@ xml_addsub(cxobj *xp, return 0; } -/*! Insert a new element (xc) under an xml node (xp), move all children to xc. - * Before: xp --> xt - * After: xp --> xc --> xt +/*! Wrap a new node between a parent xml node (xp) and all its children + * Before: xp --> xc* + * After: xp --> xw --> xc* + * @param[in] xp Parent xml node + * @param[in] tag Name of new xml child + * @retval xw Return the new child (xw) + * @see xml_addsub + * @see xml_wrap (wrap s single node) + */ +cxobj * +xml_wrap_all(cxobj *xp, + char *tag) +{ + cxobj *xw; /* new wrap node */ + + if ((xw = xml_new(tag, NULL, NULL)) == NULL) + goto done; + while (xp->x_childvec_len) + if (xml_addsub(xw, xml_child_i(xp, 0)) < 0) + goto done; + if (xml_addsub(xp, xw) < 0) + goto done; + done: + return xw; +} + +/*! Wrap a new element above a single xml node (xc) with new tag + * Before: xp --> xc # specific child + * After: xp --> xt(tag) --> xc * @param[in] xp Parent xml node * @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 + * @see xml_addsub (give the parent) + * @see xml_wrap_all (wrap all children of a node, not just one) */ cxobj * -xml_insert(cxobj *xp, - char *tag) +xml_wrap(cxobj *xc, + char *tag) { - cxobj *xc; /* new child */ + cxobj *xw; /* new wrap node */ + cxobj *xp; /* parent */ - if ((xc = xml_new(tag, NULL, NULL)) == NULL) - goto catch; - while (xp->x_childvec_len) - if (xml_addsub(xc, xml_child_i(xp, 0)) < 0) - goto catch; - if (xml_addsub(xp, xc) < 0) - goto catch; - catch: - return xc; + xp = xml_parent(xc); + if ((xw = xml_new(tag, xp, NULL)) == NULL) + goto done; + if (xml_addsub(xw, xc) < 0) + goto done; + done: + return xw; } /*! Remove and free an xml node child from xml parent @@ -958,7 +986,7 @@ xml_rm(cxobj *xc) /*! Return a child sub-tree, while removing parent and all other children * Given a root xml node, and the i:th child, remove the child from its parent - * and return it, remove the parent and all other children. + * and return it, remove the parent and all other children. (unwrap) * Before: xp-->[..xc..] * After: xc * @param[in] xp xml parent node. Will be deleted @@ -1006,7 +1034,7 @@ xml_rootchild(cxobj *xp, /*! Return a child sub-tree, while removing parent and all other children * Given a root xml node, remove the child from its parent - * , remove the parent and all other children. + * , remove the parent and all other children. (unwrap) * Before: xp-->[..xc..] * After: xc * @param[in] xp xml parent node. Must be root. Will be deleted @@ -1042,8 +1070,7 @@ xml_rootchild_node(cxobj *xp, return retval; } - -/*! help function to sorting: enumerate all children according to present order +/*! Help function to sorting: enumerate all children according to present order * This is so that the child itself know its present order in a list. * When sorting by "ordered by user", the order should remain in its present * state. @@ -1612,8 +1639,6 @@ _xml_parse(const char *str, goto done; if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) - goto done; } retval = 0; done: diff --git a/lib/src/clixon_xml_changelog.c b/lib/src/clixon_xml_changelog.c new file mode 100644 index 00000000..7fea9566 --- /dev/null +++ b/lib/src/clixon_xml_changelog.c @@ -0,0 +1,516 @@ +/* + * + ***** 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 ***** + + * YANG module revision change management. + * See draft-wang-netmod-module-revision-management-01 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_string.h" +#include "clixon_err.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_log.h" +#include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_xml_map.h" +#include "clixon_yang_module.h" +#include "clixon_xml_changelog.h" +#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 + + * @note XXX error handling! + * @note XXX xn --> xt xpath may not match +*/ +static int +changelog_op(clicon_handle h, + cxobj *xt, + cxobj *xi) + +{ + 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, "op")) == NULL) + goto ok; + /* 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; + /* Get vector of target nodes meeting the where requirement */ + if (xpath_vec(xt, "%s", &wvec, &wlen, wxpath) < 0) + goto done; + for (i=0; i..., f) || to, , NULL] + */ +int +xml_namespace_vec(clicon_handle h, + cxobj *xt, + char *namespace, + cxobj ***vecp, + size_t *veclenp) +{ + int retval = -1; + cxobj **xvec = NULL; + size_t xlen; + cxobj *xc; + char *ns; + int i; + + /* Allocate upper bound on length (ie could be too large) + a NULL element + * (event though we use veclen) + */ + xlen = xml_child_nr_type(xt, CX_ELMNT)+1; + if ((xvec = calloc(xlen, sizeof(cxobj*))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + /* Iterate and find xml nodes with assoctaed namespace */ + xc = NULL; + i = 0; + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) { + if (xml2ns(xc, NULL, &ns) < 0) /* Get namespace of XML */ + goto done; + if (strcmp(namespace, ns)) + continue; /* no match */ + xvec[i++] = xc; + } + *vecp = xvec; + *veclenp = i; + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index c20ee90c..9be09da2 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -830,7 +830,6 @@ xp_union(xp_ctx *xc1, return retval; } - /*! Evaluate an XPATH on an XML tree * 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 + * 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] xpath String with XPATH 1.0 syntax * @param[out] xrp Return XPATH context * @retval 0 OK * @retval -1 Error + * @code + * xp_ctx *xc = NULL; + * if (xpath_vec_ctx(x, xpath, &xc) < 0) + * err; + * if (xc) + * ctx_free(xc); + * @endcode */ int xpath_vec_ctx(cxobj *xcur, @@ -1295,6 +1303,7 @@ xpath_vec_flag(cxobj *xcur, } /*! 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] xpath stdarg string with XPATH 1.0 syntax * @retval 1 True diff --git a/lib/src/clixon_yang_changelog.c b/lib/src/clixon_yang_changelog.c deleted file mode 100644 index 63062e30..00000000 --- a/lib/src/clixon_yang_changelog.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * - ***** 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 ***** - - * YANG module revision change management. - * See draft-wang-netmod-module-revision-management-01 - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clixon */ -#include "clixon_queue.h" -#include "clixon_hash.h" -#include "clixon_string.h" -#include "clixon_err.h" -#include "clixon_handle.h" -#include "clixon_yang.h" -#include "clixon_log.h" -#include "clixon_xml.h" -#include "clixon_options.h" -#include "clixon_xml_map.h" -#include "clixon_yang_module.h" -#include "clixon_yang_changelog.h" -#include "clixon_xpath_ctx.h" -#include "clixon_xpath.h" - -#if 0 -/*! Make a specific change - 0001 - create - - - /a:system/a:y; - - -*/ -static int -upgrade_op(cxobj *x) -{ - int retval = -1; - - xml_print(stderr, x); - retval = 0; - // done: - return retval; -} - -static int -upgrade_deleted(clicon_handle h, - char *name, - cxobj *xs) -{ - int retval = -1; - - fprintf(stderr, "%s \"%s\" belongs to a removed module\n", __FUNCTION__, name); - retval = 0; - // done: - return retval; -} - -/*! - * @param[in] xs Module state - */ -static int -upgrade_modified(clicon_handle h, - char *name, - char *namespace, - cxobj *xs, - cxobj *xch) -{ - int retval = -1; - char *mname; - yang_spec *yspec = NULL; - yang_stmt *ymod; - yang_stmt *yrev; - char *mrev; - cxobj **vec = NULL; - size_t veclen; - int i; - - fprintf(stderr, "%s: \"%s\" belongs to an upgraded module\n", __FUNCTION__, name); - yspec = clicon_dbspec_yang(h); - - /* We need module-name of XML since changelog uses that (change in changelog?)*/ - mname = xml_find_body(xs, "name"); - - /* Look up system module (alt send it via argument) */ - if ((ymod = yang_find_module_by_name(yspec, mname)) == NULL) - goto done; - if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) == NULL) - goto done; - mrev = yrev->ys_argument; - /* Look up in changelog */ - - if (xpath_vec(xch, "module[name=\"%s\" and revision=\"%s\"]/revision-change-log", - &vec, &veclen, mname, mrev) < 0) - goto done; - /* Iterate through changelog */ - for (i=0; i..., ys_argument, &to) < 0) + goto done; + } + if ((ret = upgrade_callback_call(h, xt, ns, from, to, cbret)) < 0) + goto done; + if (ret == 0) /* XXX ignore and continue? */ + goto fail; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + /*! Upgrade XML * @param[in] h Clicon handle * @param[in] xt XML tree (to upgrade) * @param[in] msd Modules-state differences of xt + * @param[out] cbret Netconf error message if invalid * @retval 1 OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error */ int @@ -390,67 +450,37 @@ clixon_module_upgrade(clicon_handle h, cbuf *cbret) { int retval = -1; - cxobj *xc; /* XML child of data */ - char *namespace; - cxobj *xs; /* XML module state */ - char *xname; /* XML top-level symbol name */ - int state; /* 0: no changes, 1: deleted, 2: modified */ - char *modname; - yang_spec *yspec; - yang_stmt *ymod; - yang_stmt *yrev; - char *rev; - uint32_t from; - uint32_t to; + char *ns; /* Namespace */ + cxobj *xs; /* XML module state */ int ret; - /* Iterate through db XML top-level - get namespace info */ - xc = NULL; - while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) { - xname = xml_name(xc); /* xml top-symbol name */ - if (xml2ns(xc, NULL, &namespace) < 0) /* Get namespace of XML */ - goto done; - if (namespace == NULL){ - clicon_log(LOG_DEBUG, "XML %s lacks namespace", xname); - goto fail; - } - /* Look up module-state via namespace of XML */ - state = 0; /* XML matches system modules */ - if (msd){ - if ((xs = xpath_first(msd->md_del, "module[namespace=\"%s\"]", namespace)) != NULL) - state = 1; /* XML belongs to a removed module */ - else if ((xs = xpath_first(msd->md_mod, "module[namespace=\"%s\"]", namespace)) != NULL) - state = 2; /* XML belongs to an outdated module */ - } - /* Pick up more data from data store module-state */ - from = to = 0; - modname = NULL; - if (state && xs && msd){ /* sanity: XXX what about no msd?? */ - modname = xml_find_body(xs, "name"); /* Module name */ - if ((rev = xml_find_body(xs, "revision")) != NULL) /* Module revision */ - if (ys_parse_date_arg(rev, &from) < 0) - goto done; - if (state > 1){ - yspec = clicon_dbspec_yang(h); - /* Look up system module (alt send it via argument) */ - if ((ymod = yang_find_module_by_name(yspec, modname)) == NULL) - goto fail; - if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) == NULL) - goto fail; - if (ys_parse_date_arg(yrev->ys_argument, &to) < 0) - goto done; - - } - } - /* Make upgrade callback for this XML, specifying the module name, - * namespace, from and to revision. - * XXX: namespace may be known but not module!! - */ - if ((ret = upgrade_callback_call(h, xc, modname, namespace, from, to, NULL)) < 0) + if (msd == NULL) + goto ok; + /* Iterate through xml modified module state */ + xs = NULL; + while ((xs = xml_child_each(msd->md_mod, xs, CX_ELMNT)) != NULL) { + /* Extract namespace */ + if ((ns = xml_find_body(xs, "namespace")) == NULL) + goto done; + /* Extract revisions and make callbacks */ + if ((ret = mod_ns_upgrade(h, xt, xs, ns, 0, cbret)) < 0) goto done; if (ret == 0) goto fail; } + /* Iterate through xml deleted module state */ + xs = NULL; + while ((xs = xml_child_each(msd->md_del, xs, CX_ELMNT)) != NULL) { + /* Extract namespace */ + if ((ns = xml_find_body(xs, "namespace")) == NULL) + continue; + /* Extract revisions and make callbacks (now w deleted=1) */ + if ((ret = mod_ns_upgrade(h, xt, xs, ns, 1, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ok: retval = 1; done: return retval; diff --git a/test/test_order.sh b/test/test_order.sh index 07275b42..05f564ae 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -36,7 +36,6 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/lib/example/backend /usr/local/var/$APPNAME/$APPNAME.pidfile - 1 $dbdir /usr/local/lib/xmldb/text.so diff --git a/test/test_upgrade_changelog.sh b/test/test_upgrade_auto.sh similarity index 51% rename from test/test_upgrade_changelog.sh rename to test/test_upgrade_auto.sh index e753afcf..50f52849 100755 --- a/test/test_upgrade_changelog.sh +++ b/test/test_upgrade_auto.sh @@ -3,7 +3,7 @@ # Ways of changes (operation-type) are: # create, delete, move, modify # In this example, example-a has the following changes: -# - Create y, delete x, modify host-name, move z +# - Create y, delete x, replace host-name, move z # example-b is completely obsoleted # Magic line must be first in script (see README.md) @@ -16,84 +16,111 @@ changelog=$dir/changelog.xml # Module revision changelog changelog2=$dir/changelog2.xml # From draft appendix exa01y=$dir/example-a@2017-12-01.yang exa20y=$dir/example-a@2017-12-20.yang +exb20y=$dir/example-b@2017-12-20.yang # draft-wang-netmod-module-revision-management-01 # 3.2.1 and 4.1 example-a revision 2017-12-01 cat < $exa01y - module example-a{ - yang-version 1.1; - namespace "urn:example:a"; - prefix "a"; +module example-a{ + yang-version 1.1; + namespace "urn:example:a"; + prefix "a"; - organization "foo."; - contact "fo@example.com"; - description - "foo."; + organization "foo."; + contact "fo@example.com"; + description + "foo."; - revision 2017-12-01 { - description "Initial revision."; - } + revision 2017-12-01 { + description "Initial revision."; + } - container system { - leaf a { - type string; - description "no change"; - } - leaf x { - type string; - description "delete"; - } - leaf host-name { - type uint32; - description "modify type"; - } - leaf z { - description "move to alt"; - type string; - } - } - container alt { - } - } + container system { + leaf a { + type string; + description "no change"; + } + leaf b { + type string; + description "rename tag"; + } + leaf x { + type string; + description "delete"; + } + leaf host-name { + type uint32; + description "modify type"; + } + leaf z { + description "move to alt"; + type string; + } + } + container alt { + } +} EOF # 3.2.1 and 4.1 example-a revision 2017-12-20 cat < $exa20y - module example-a{ - yang-version 1.1; - namespace "urn:example:a"; - prefix "a"; +module example-a { + yang-version 1.1; + namespace "urn:example:a"; + prefix "a"; - organization "foo."; - contact "fo@example.com"; - description - "foo."; + organization "foo."; + contact "fo@example.com"; + description + "foo."; - revision 2017-12-20 { - description "Create y, delete x, modify host-name, move z"; - } - revision 2017-12-01 { - description "Initial revision."; - } - container system { - leaf a { - type string; - description "no change"; - } - leaf host-name { - type string; - description "modify type"; - } - leaf y { - type string; - description "create"; - } - } - container alt { - leaf z { - description "move to alt"; - type string; - } - } + revision 2017-12-20 { + description "Create y, delete x, replace host-name, move z"; + } + revision 2017-12-01 { + description "Initial revision."; + } + container system { + leaf a { + type string; + description "no change"; + } + leaf c { + type string; + description "rename tag"; + } + leaf host-name { + type string; + description "replace"; + } + leaf y { + type string; + description "create"; + } + } + container alt { + leaf z { + description "move to alt"; + type string; + } + } +} +EOF + +# 3.2.1 and 4.1 example-a revision 2017-12-20 +cat < $exb20y +module example-b { + yang-version 1.1; + namespace "urn:example:b"; + prefix "b"; + + organization "foo."; + contact "fo@example.com"; + description + "foo."; + + revision 2017-12-20 { + description "Remove all"; + } } EOF @@ -125,16 +152,23 @@ cat < $dir/startup_db dont change me + rename me modify me remove me move me + + Obsolete EOF +# Wanted new XML +XML='dont change merename mei am modifiedcreatedmove me' + + # Create configuration cat < $cfg @@ -147,8 +181,8 @@ cat < $cfg $dir /usr/local/lib/xmldb/text.so true - true - $changelog + true + $changelog /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME @@ -157,54 +191,52 @@ EOF # Changelog of example-a: cat < $changelog - - - example-b - 2017-12-01 - - - - example-a + + + urn:example:b + 2017-12-01 2017-12-20 - true - - 0001 - create - - - /a:system/a:y; - - - - - 0002 - delete - - - /a:system/a:x; - - - - - 0003 - modify - - - /a:system/a:host-name; - - - - - 0004 - move - - - /a:system/a:z; - - - - - + + 1 + delete + /b:system-b + + + + urn:example:a + 2017-12-01 + 2017-12-20 + + 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 @@ -234,9 +266,8 @@ start_restconf -f $cfg new "waiting" sleep $RCWAIT -new "Check failsafe (work in progress)" new "Check running db content" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^Failsafe]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$XML]]>]]>$" new "Kill restconf daemon" stop_restconf diff --git a/test/test_upgrade_interfaces.sh b/test/test_upgrade_interfaces.sh new file mode 100755 index 00000000..ebbcb8e4 --- /dev/null +++ b/test/test_upgrade_interfaces.sh @@ -0,0 +1,333 @@ +#!/bin/bash +# Upgrade a module by registering a manually programmed callback +# The usecase is insipred by the ietf-interfaces upgrade from +# 2014-05-08 to 2018-02-20. +# That includes moving parts from interfaces-state to interfaces and then +# deprecating the whole /interfaces-state tree. +# A preliminary change list is in Appendix A of +# draft-wang-netmod-module-revision-management-01 +# The example here is simplified and also extended. +# It has also been broken up into two parts to test a series of upgrades. +# These are the operations (authentic): +# Move /if:interfaces-state/if:interface/if:admin-status to +# /if:interfaces/if:interface/ +# Move /if:interfaces-state/if:interface/if:statistics to +# if:interfaces/if:interface/ +# Delete /if:interfaces-state +# These are extra added for test: +# Rename /interfaces/interface/description to /interfaces/interface/descr +# Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr +# Change type /interfaces/interface/statistics/in-octets to decimal64 and divide all values with 1000 +# + +# 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 + +cfg=$dir/conf.xml +if2014=$dir/interfaces@2014-05-08.yang +if2018=$dir/interfaces@2018-02-20.yang + +# Original simplified version - note all is config to allow for storing in +# datastore +cat < $if2014 +module interfaces{ + yang-version 1.1; + namespace "urn:example:interfaces"; + prefix "if"; + + import ietf-yang-types { + prefix yang; + } + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + container interfaces { + description + "Interface configuration parameters."; + + list interface { + key "name"; + leaf name { + type string; + } + leaf description { + type string; + } + leaf type { + type string; + mandatory true; + } + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled; + enum disabled; + } + } + } + } + container interfaces-state { + list interface { + key "name"; + leaf name { + type string; + } + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up; + enum down; + enum testing; + } + mandatory true; + } + container statistics { + leaf in-octets { + type yang:counter64; + } + leaf in-unicast-pkts { + type yang:counter64; + } + } + } + } +} + +EOF + +cat < $if2018 +module interfaces{ + yang-version 1.1; + namespace "urn:example:interfaces"; + prefix "if"; + + import ietf-yang-types { + prefix yang; + } + revision 2018-02-20 { + description + "Updated to support NMDA."; + reference + "RFC 8343: A YANG Data Model for Interface Management"; + } + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + container interfaces { + description + "Interface configuration parameters."; + + list interface { + key "name"; + leaf name { + type string; + } + container docs{ + description "Original description is wrapped and renamed"; + leaf descr { + type string; + } + } + leaf type { + type string; + mandatory true; + } + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled; + enum disabled; + } + } + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up; + enum down; + enum testing; + } + mandatory true; + } + container statistics { + leaf in-octets { + type decimal64{ + fraction-digits 3; + } + } + leaf in-unicast-pkts { + type yang:counter64; + } + } + } + } +} +EOF + + +# Create startup db revision example-a and example-b 2017-12-01 +# this should be automatically upgraded to 2017-12-20 +cat < $dir/startup_db + + + 42 + + interfaces + 2014-05-08 + urn:example:interfaces + + + + + e0 + eth + First interface + + + e1 + eth + + + + + e0 + up + + 54326432 + 8458765 + + + + e1 + down + + + e2 + testing + + + +EOF + +# Wanted new XML +# Note interface e2 is not moved +cat < $dir/wanted + + + 42 + + interfaces + 2018-02-20 + urn:example:interfaces + + + + + e0 + eth + up + First interface + + 54326.432 + 8458765 + + + + e1 + eth + down + + + +EOF + +XML='e0First interfaceethup54326.4328458765e1ethdown' + + +# Create configuration +cat < $cfg + + $cfg + /usr/local/share/clixon + *:* + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/example/backend + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + /usr/local/lib/xmldb/text.so + true + false + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + +EOF + +# Start new system from old datastore +mode=startup + +# -u means trigger example upgrade +new "test params: -s $mode -f $cfg -- -u" +# 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 -- -u" + start_backend -s $mode -f $cfg -- -u +fi +new "waiting" +sleep $RCWAIT + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +start_restconf -f $cfg + +new "waiting" +sleep $RCWAIT + +new "Check running db content" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$XML]]>]]>$" + +new "Kill restconf daemon" +stop_restconf + +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 + + rm -rf $dir +fi diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index d176e1cc..bd541876 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -43,7 +43,7 @@ CLIXON_DATADIR = @CLIXON_DATADIR@ YANGSPECS = clixon-config@2019-03-05.yang YANGSPECS += clixon-lib@2019-01-02.yang YANGSPECS += clixon-rfc5277@2008-07-01.yang -YANGSPECS += clixon-yang-changelog@2019-03-21.yang +YANGSPECS += clixon-xml-changelog@2019-03-21.yang APPNAME = clixon # subdir ehere these files are installed diff --git a/yang/clixon/clixon-config@2019-03-05.yang b/yang/clixon/clixon-config@2019-03-05.yang index bbf62324..7987e8a2 100644 --- a/yang/clixon/clixon-config@2019-03-05.yang +++ b/yang/clixon/clixon-config@2019-03-05.yang @@ -392,6 +392,18 @@ module clixon-config { info. When loaded at startup, a check is made if the system yang modules match"; } + leaf CLICON_XML_CHANGELOG { + type boolean; + default false; + description "If true enable automatic upgrade using yang clixon + changelog."; + } + leaf CLICON_XML_CHANGELOG_FILE { + type string; + description "Name of file with module revision changelog. + If CLICON_XML_CHANGELOG is true, Clixon + reads the module changelog from this file."; + } leaf CLICON_USE_STARTUP_CONFIG { type int32; default 0; @@ -428,18 +440,6 @@ module clixon-config { data. If enabled, module info will appear when doing netconf get or restconf GET"; } - leaf CLICON_YANG_CHANGELOG { - type boolean; - default false; - description "If true enable automatic upgrade using yang clixon - changelog."; - } - leaf CLICON_YANG_CHANGELOG_FILE { - type string; - description "Name of file with module revision changelog. - If CLICON_YANG_CHANGELOG is true, Clixon - reads the module changelog from this file."; - } leaf CLICON_MODULE_SET_ID { type string; default "0"; diff --git a/yang/clixon/clixon-xml-changelog@2019-03-21.yang b/yang/clixon/clixon-xml-changelog@2019-03-21.yang new file mode 100644 index 00000000..4533c2e7 --- /dev/null +++ b/yang/clixon/clixon-xml-changelog@2019-03-21.yang @@ -0,0 +1,145 @@ +module clixon-xml-changelog { + yang-version 1.1; + namespace "http://clicon.org/xml-changelog"; + prefix ml; + + import ietf-yang-library { + prefix yanglib; + } + import ietf-yang-types { + prefix yang; + } + + organization "Clixon"; + contact + "Olof Hagsand "; + description + "This is experimental XML changelog module with several influences: + 1) draft-wang-netmod-module-revision-management-01, by: + Qin Wu + Zitao Wang + 2) XProc https://www.w3.org/TR/xproc/#xpath-context"; + + revision 2019-03-21 { + description + "Initial Clixon derived version"; + } + + typedef operation_type { + description + "From: https://en.wikipedia.org/wiki/XML_pipeline: + Rename - renames elements or attributes without modifying the content + Replace - replaces elements or attributes + Insert - adds a new data element to the output stream at a specified point + Delete - removes an element or attribute (also known as pruning the input tree) + Wrap - wraps elements with additional elements + Reorder - changes the order of elements + More inspiration in XProc: https://www.w3.org/TR/xproc/#ex2"; + type enumeration{ + enum rename { + 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. + Synopsis: replace(where:targets, when:bool, new:xml)"; + } + enum insert { + 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. + Synopsis: delete(where:parents, when:bool)"; + } + enum move { + description + "Move the target node(Added). + Synopsis: move(where:parents, when:bool, dst:node)"; + } + } + } + container changelogs { + config false; + list changelog { + key "namespace revision"; + leaf namespace { + type string; + description + "The YANG namespace identifying a module or submodule. + XML needs to be identified by namespace, translation to + module name may not always be possible."; + } + leaf revision { + type yanglib:revision-identifier; + description + "The YANG module or submodule revision date. + This is the actual date of the changlelog items. + Note however if the terminate flag is set, this is a virtual + revision just in place to terminate the XML, such as removing or + moving items,."; + } + leaf revfrom { + type yanglib:revision-identifier; + description + "Optional revision from date. This changelog is effective in the + range [from,to]. If from is not given the changelog is open-ended. + Several changelogs may be applied if the upgrade spans multiple + ranges: [from0,to0],..[fromN,toN]"; + } + list step { + description + "List for module revision change log"; + key "name"; + leaf name { + type string; + description + "Unique step name"; + } + leaf op { + type operation_type; + mandatory true; + description + "This leaf indicate the change operation, such as create, move, delete, modify, etc."; + } + leaf where { + type yang:xpath1.0; + mandatory true; + description + "Identifies the target data node for update. + for move, modify or delete the target-node points to + the data node of the old version. + For create, it is the parent where it should be + inserted."; + } + leaf when { + type yang:xpath1.0; + description + "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; + } + leaf dst { + description + "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."; + } + } + } + } +} diff --git a/yang/clixon/clixon-yang-changelog@2019-03-21.yang b/yang/clixon/clixon-yang-changelog@2019-03-21.yang deleted file mode 100644 index 01e5ecc3..00000000 --- a/yang/clixon/clixon-yang-changelog@2019-03-21.yang +++ /dev/null @@ -1,326 +0,0 @@ -module clixon-yang-changelog { - yang-version 1.1; - namespace "http://clicon.org/yang-changelog"; - prefix ml; - - import ietf-yang-library { - prefix yanglib; - } - import ietf-yang-types { - prefix yang; - } - - organization "Clixon"; - contact - "Olof Hagsand "; - description - "This is experimentalYANG changelog derived from: - draft-wang-netmod-module-revision-management-01 - with the following contacts and references: - WG Web: - - WG List: - - Author: Qin Wu - - Zitao Wang - "; - reference "draft-wang-netmod-module-revision-management-01"; - revision 2019-03-21 { - description - "Initial Clixon derived version"; - } - - identity operation-type { - description - "Abstract base identity for the operation type "; - } - - identity create { - base operation-type; - description - "Denotes create new data nodes"; - } - - identity delete { - base operation-type; - description - "Denotes delete the target node"; - } - - identity move { - base operation-type; - description - "Denote move the target node."; - } - - identity modify { - base operation-type; - description - "Denote modify the target data node."; - } - - identity statement-type { - description - "Base identity for statement type"; - } - - identity feature-statement { - base statement-type; - description - "feature statement, if this type be chose, it means that the - feature or if-feature statement been modified"; - } - identity identity-statement { - base statement-type; - description - "identity statement, if this type be chose, it means that the - identity statement been modified, for example, add new identity, etc."; - } - - identity grouping-statement { - base statement-type; - description - "grouping statement, if this type be chose, it means that the grouping - statement been modified."; - } - - identity typedef-statement { - base statement-type; - description - "typedef statement, if this type be chose, it means that the typedef - statement been modified."; - } - - identity augment-statement { - base statement-type; - description - "augment statement, if this type be chose, it means that the augment - statement been modified."; - } - - identity rpc-statement { - base statement-type; - description - "rpc statement, if this type be chose, it means that the rpc - statement been modified."; - } - - identity notification-statement { - base statement-type; - description - "notification statement, if this type be chose, it means that the notification - statement been modified."; - } - - extension purpose { - argument name; - description - "The purpose can be used to mark the data nodes change purpose. - The name argument can be specified in the following recommended mode - - bug-fix, which can help user to understand the data nodes' changes present bug fix, - - new-function, which can help user to understand the data nodes' changes present new function, - - nmda-conform, which can help user to understand the data nodes' changes conform to NMDA, - - and note that the user can argument the purpose name according to their sepcific requirements."; - } - - grouping data-definition { - container data-definition { - leaf target-node { - type yang:xpath1.0; - mandatory true; - description - "Identifies the target data node for update. - Notice that, if the update-type equal to move or delete, - this target-node must point to the data node of old version. - \t - For example, suppose the target node is a YANG leaf named a, - and the previous version is: - \t - container foo { - leaf a { type string; } - leaf b { type int32; } - } - \t - the new version is: - container foo { - leaf b {type int32;} - } - \t - Therefore, the targe-node should be /foo/a."; - } - leaf location-point { - type yang:xpath1.0; - description - "Identifies the location point where the updates happened."; - } - leaf where { - when "derived-from-or-self(../../change-operation, 'move')" { - description - "This leaf only applies for 'move' - updates."; - } - type enumeration { - enum "before" { - description - "Insert or move a data node before the data resource - identified by the 'point' parameter."; - } - enum "after" { - description - "Insert or move a data node after the data resource - identified by the 'point' parameter."; - } - enum "first" { - description - "Insert or move a data node so it becomes ordered - as the first entry."; - } - enum "last" { - description - "Insert or move a data node so it becomes ordered - as the last entry."; - } - } - default "last"; - description - "Identifies where a data resource will be inserted - or moved."; - } - anydata data-definition { - when "derived-from-or-self(../../change-operation, 'modify')" { - description - "This nodes only be present when - the 'change-operation' equal to 'modify'."; - } - description - "This nodes used for present the definitions before updated. - And this nodes only be present when - the 'change-operation' equal to 'modify'."; - } - description - "Container for data statement"; - } - description - "Grouping for data definition"; - } - - grouping other-statement { - container other-statement { - leaf statement-name { - type identityref { - base statement-type; - } - description - "Statement name, for example, identity, feature, typedef, etc."; - } - anydata statement-definition { - description - "This nodes used for present new the definitions."; - } - list substatements { - key "statement-name"; - leaf statement-name { - type identityref { - base statement-type; - } - description - "Statement name, for example, identity, feature, typedef, etc."; - } - anydata substatement-definition { - description - "This nodes used for present new the definitions."; - } - description - "List for substatements updates"; - } - description - "Container for header statement updates"; - } - description - "Grouping for header statement"; - } - - grouping change-log { - list revision-change-log { - key "index"; - leaf index { - type uint32; - description - "Index for module change log"; - } - leaf change-operation { - type identityref { - base operation-type; - } - mandatory true; - description - "This leaf indicate the change operation, such as create, move, delete, modify, etc."; - } - choice yang-statements { - description - "Choice for various YANG statements that have been impacted."; - case data-definition-statement { - uses data-definition; - } - case other-statement { - uses other-statement; - } - } - description - "List for module revision change log"; - } - description - "Grouping for module revision change log"; - } - - container yang-modules { - config false; - list module { - key "name revision"; - leaf name { - type yang:yang-identifier; - description - "The YANG module or submodule name."; - } - leaf revision { - type yanglib:revision-identifier; - description - "The YANG module or submodule revision date. If no revision - statement is present in the YANG module or submodule, this - leaf is not instantiated."; - } - leaf backward-compatible { - type boolean; - description - "Indicates whether it is a backward compatible version. - If this parameter is set to true, it means that this version is - a backwards compatible version"; - } - uses change-log; - description - "List for module updated log"; - } - description - "This container present the modules updated log."; - } - augment "/yanglib:yang-library/yanglib:module-set/yanglib:module" { - description - "Augment the yang library with backward compatibility indication."; - leaf backward-compatible { - type boolean; - description - "backward compatibility indication."; - } - } - augment "/yanglib:yang-library/yanglib:module-set/yanglib:module/yanglib:submodule" { - description - "Augment the yang library with backward compatibility indication."; - leaf backward-compatible { - type boolean; - description - "backward compatibility indication."; - } - } -}