diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 890be1d5..f42a08c5 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -197,14 +197,14 @@ startup_common(clicon_handle h, xt = NULL; goto ok; } - if (msd){ - /* Here xt is old syntax */ - if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; - /* Here xt is new syntax */ - } + if (clixon_plugin_xmldb_repair(h, db, xt) < 0) + goto done; + /* Here xt is old syntax */ + 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; diff --git a/example/main/example_backend.c b/example/main/example_backend.c index b8db088d..440d2d92 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -173,6 +173,98 @@ main_abort(clicon_handle h, return 0; } +struct map_str2str{ + char *ms_path; + char *ms_ns; +}; +static const struct map_str2str path_namespace_map[] = { + {"/a:x/a:y/a:z/a:w", "urn:example:b"}, + {"/a:x/a:y/a:z", "urn:example:b"}, + {NULL, NULL} +}; + +static int +main_repair_one(cxobj *xt, + char *mypath, + char *mynamespace, + cvec *nsc) +{ + int retval = -1; + cxobj **xvec = NULL; + size_t xlen; + char *myprefix = NULL; + int i; + cxobj *x; + char *namespace; + char *pexist = NULL; /* Existing prefix */ + + if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, mypath) < 0) + goto done; + if (xml_nsctx_get_prefix(nsc, mynamespace, &myprefix) == 0){ + clicon_err(OE_XML, ENOENT, "Namespace %s not found in canonical namespace map", + mynamespace); + goto done; + } + for (i=0; ims_path; ms++) + if (main_repair_one(xt, ms->ms_path, ms->ms_ns, nsc) < 0) + goto done; + ok: + retval = 0; + done: + if (nsc) + cvec_free(nsc); + return retval; +} + /*! Routing example notification timer handler. Here is where the periodic action is */ static int @@ -684,7 +776,8 @@ static clixon_plugin_api api = { .ca_trans_commit=main_commit, /* trans commit */ .ca_trans_revert=main_revert, /* trans revert */ .ca_trans_end=main_end, /* trans end */ - .ca_trans_abort=main_abort /* trans abort */ + .ca_trans_abort=main_abort, /* trans abort */ + .ca_xmldb_repair=main_repair /* xmldb repair. */ }; /*! Backend plugin initialization diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 2b8445c1..092f8d8b 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -159,6 +159,16 @@ typedef int (trans_cb_t)(clicon_handle h, transaction_data td); */ typedef char *(cli_prompthook_t)(clicon_handle, char *mode); +/*! Datastore repair callback + * Gets called on startup after initial XML parsing, but before upgrading and before validation + * @param[in] h Clicon handle + * @param[in] db Name of datastore, eg "running" + * @param[in] xt XML tree. Repair this "in place" + * @retval -1 Error + * @retval 0 OK + */ +typedef int (xmldb_repair_t)(clicon_handle h, char *db, cxobj *xt); + /*! Startup status for use in startup-callback * Note that for STARTUP_ERR and _INVALID, running runs in failsafe mode * and startup contains the erroneous or invalid database. @@ -206,8 +216,9 @@ struct clixon_plugin_api{ trans_cb_t *cb_trans_revert; /* Transaction revert */ trans_cb_t *cb_trans_end; /* Transaction completed */ trans_cb_t *cb_trans_abort; /* Transaction aborted */ - } cau_backend; + xmldb_repair_t *cb_xmldb_repair; /* Repair datastore on load */ + } cau_backend; } u; }; /* Access fields */ @@ -224,6 +235,8 @@ struct clixon_plugin_api{ #define ca_trans_revert u.cau_backend.cb_trans_revert #define ca_trans_end u.cau_backend.cb_trans_end #define ca_trans_abort u.cau_backend.cb_trans_abort +#define ca_xmldb_repair u.cau_backend.cb_xmldb_repair + /* * Macros @@ -271,6 +284,8 @@ int clixon_plugin_auth(clicon_handle h, void *arg); int clixon_plugin_extension(clicon_handle h, yang_stmt *yext, yang_stmt *ys); +int clixon_plugin_xmldb_repair(clicon_handle h, char *db, cxobj *xt); + /* rpc callback API */ int rpc_callback_register(clicon_handle h, clicon_rpc_cb cb, void *arg, char *namespace, char *name); int rpc_callback_delete_all(clicon_handle h); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 9a5107a2..00731904 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -56,6 +56,7 @@ int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1, cxobj ***changed_x0, cxobj ***changed_x1, size_t *changedlen); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged(cxobj *xt, int flag, int test); +int add_namespace(cxobj *x1, cxobj *x1p, char *prefix1, char *namespace); int xml_default(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 8bfdefd8..d28230f7 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -467,6 +467,33 @@ clixon_plugin_extension(clicon_handle h, } return retval; } +/*! Call plugin module repair in all plugins + * + * Repair datastore on load before or as an alternative to the upgrading mechanism + * @param[in] h Clicon handle + * Call plugin start functions (if defined) + * @note Start functions used to have argc/argv. Use clicon_argv_get() instead + */ +int +clixon_plugin_xmldb_repair(clicon_handle h, + char *db, + cxobj *xt) +{ + clixon_plugin *cp; + int i; + xmldb_repair_t *repairfn; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((repairfn = cp->cp_api.ca_xmldb_repair) == NULL) + continue; + if (repairfn(h, db, xt) < 0) { + clicon_debug(1, "%s() failed", __FUNCTION__); + return -1; + } + } + return 0; +} /*-------------------------------------------------------------------- * RPC callbacks for both client/frontend and backend plugins. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2d822638..52565c3c 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -708,7 +708,7 @@ xml_tree_prune_flagged(cxobj *xt, /*! Add prefix:namespace pair to xml node, set cache, prefix, etc */ -static int +int add_namespace(cxobj *x1, /* target */ cxobj *x1p, char *prefix1, diff --git a/test/test_datastore_repair.sh b/test/test_datastore_repair.sh new file mode 100755 index 00000000..800fb8a7 --- /dev/null +++ b/test/test_datastore_repair.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Keep a vector of {xpath, namespace} pairs, match xpath in parsed XML and check if symbols belong to namespace +# If not, set that namespace. +# This is a primitive upgrade mechanism if th emore general upgrade mechanism can not be used + +# 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_yang.xml +fyangA=$dir/A.yang +fyangB=$dir/B.yang + +# Create configuration +cat < $cfg + + $cfg + ietf-netconf:startup + /usr/local/share/clixon + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/example/backend + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + true + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + +EOF + +# Yang module A (base) +cat < $fyangA +module A{ + prefix a; + revision 2020-02-11; + namespace "urn:example:a"; + container x { + container y { + } + } +} +EOF + +# Yang module B (augments A) +cat < $fyangB +module B{ + prefix b; + revision 2020-02-11; + namespace "urn:example:b"; + import A { + prefix "a"; + } + augment "/a:x/ngrt:y" { + container z { + leaf w { + type string; + } + } + } +} +EOF + +# permission kludges +sudo touch $dir/startup_db +sudo chmod 666 $dir/startup_db +# Create startup db of "old" db with incorrect augment namespace tagging +cat < $dir/startup_db + + + + + foo + + + + +EOF + +# This is how it should look after repair, using prefixes +AFTER=$(cat <foo +EOF +) + +# Or using default: +# foo + +new "test params: -f $cfg" +# 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 startup -f $cfg" + start_backend -s startup -f $cfg + + new "waiting" + wait_backend +fi + +new "netconf get config" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^$AFTER]]>]]>$" + +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