diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index df545f18..7bdfbd38 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -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 */ @@ -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 07f7ed6a..98697664 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -304,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 94b3dd37..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,7 +254,7 @@ example_statedata(clicon_handle h, return retval; } -/*! 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] ns Namespace of module (for info) @@ -258,21 +266,131 @@ example_statedata(clicon_handle h, * @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 *xt, - char *ns, - 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) { 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..., " 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 @@ -363,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; } @@ -409,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 @@ -464,9 +620,18 @@ clixon_plugin_init(clicon_handle h) "copy-config" ) < 0) goto done; - /* General purpose upgrade callback */ - if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 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_options.h b/lib/clixon/clixon_options.h index 28e36551..8abf3b2a 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -214,4 +214,8 @@ int clicon_module_state_set(clicon_handle h, cxobj *xms); 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_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/src/clixon_options.c b/lib/src/clixon_options.c index 16ff2750..6862605f 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -1046,3 +1046,60 @@ clicon_xml_changelog_set(clicon_handle h, 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_xml.c b/lib/src/clixon_xml.c index 19602df7..c4d561ad 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -808,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, @@ -836,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, should be called "wrap" + * @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 @@ -961,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 @@ -1009,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 @@ -1045,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. diff --git a/test/test_upgrade_changelog.sh b/test/test_upgrade_auto.sh similarity index 99% rename from test/test_upgrade_changelog.sh rename to test/test_upgrade_auto.sh index aaf0143c..50f52849 100755 --- a/test/test_upgrade_changelog.sh +++ b/test/test_upgrade_auto.sh @@ -266,7 +266,6 @@ 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 ']]>]]>' "^$XML]]>]]>$" 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