XML chanelog revision

This commit is contained in:
Olof hagsand 2019-03-25 10:42:27 +01:00
parent 3f68cca06c
commit a0abf8436e
24 changed files with 948 additions and 848 deletions

View file

@ -21,11 +21,11 @@
* A user can register upgrade callbacks per module/revision when in-compatible XML is encountered (`update_callback_register`).
* 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
* 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_YANG_CHANGELOG enables the yang changelog feature
* CLICON_YANG_CHANGELOG_FILE where the changelog resides
* CLICON_XML_CHANGELOG enables the yang changelog feature
* CLICON_XML_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

View file

@ -252,7 +252,9 @@ validate_common(clicon_handle h,
* 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
* @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
@ -268,12 +270,14 @@ validate_common(clicon_handle h,
int
startup_validate(clicon_handle h,
char *db,
cxobj **xtr,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
modstate_diff_t *msd = NULL;
cxobj *xt = NULL;
transaction_data_t *td = NULL;
/* Handcraft a transition with only target and add trees */
@ -287,14 +291,23 @@ startup_validate(clicon_handle h,
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)
if (xmldb_get(h, db, "/", 1, &xt, msd) < 0)
goto done;
if ((ret = clixon_module_upgrade(h, td->td_target, msd, cbret)) < 0)
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;
@ -302,10 +315,6 @@ startup_validate(clicon_handle h,
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)
@ -320,6 +329,112 @@ startup_validate(clicon_handle h,
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
if (xtr){
*xtr = td->td_target;
td->td_target = NULL;
}
retval = 1;
done:
if (td)
transaction_free(td);
if (msd)
modstate_diff_free(msd);
return retval;
fail: /* cbret should be set */
if (cbuf_len(cbret)==0){
clicon_err(OE_CFG, EINVAL, "Validation fail but cbret not set");
goto done;
}
retval = 0;
goto done;
}
int
startup_commit(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int ret;
modstate_diff_t *msd = NULL;
cxobj *xt = NULL;
transaction_data_t *td = NULL;
/* Handcraft a transition with only target and add trees */
if ((td = transaction_new()) == NULL)
goto done;
/* 2. Parse xml trees
* This is the state we are going to
* Note: xmsdiff contains non-matching modules
* Only if CLICON_XMLDB_MODSTATE is enabled
*/
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msd = modstate_diff_new()) == NULL)
goto done;
if (xmldb_get(h, db, "/", 1, &xt, msd) < 0)
goto done;
if (msd && (ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, 0, "Yang spec not set");
goto done;
}
/* After upgrading, XML tree needs to be sorted and yang spec populated */
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
/* Handcraft transition with with only add tree */
td->td_target = xt;
if (cxvec_append(td->td_target, &td->td_avec, &td->td_alen) < 0)
goto done;
/* 4. Call plugin transaction start callbacks */
if (plugin_transaction_begin(h, td) < 0)
goto done;
/* 5. Make generic validation on all new or changed data.
Note this is only call that uses 3-values */
if ((ret = generic_validate(yspec, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail; /* STARTUP_INVALID */
/* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0)
goto done;
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
/* 8. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
goto done;
/* 9, write (potentially modified) tree to running
* XXX note here startup is copied to candidate, which may confuse everything
*/
if ((ret = xmldb_put(h, "running", OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 10. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 11. Copy running back to candidate in case end functions updated running
* XXX: room for improvement: candidate and running may be equal.
* Copy only diffs?
*/
if (xmldb_copy(h, "running", "candidate") < 0){
/* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
retval = 1;
done:
if (td)

View file

@ -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);

View file

@ -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);
@ -643,8 +643,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 */

View file

@ -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;

View file

@ -246,7 +246,6 @@ example_statedata(clicon_handle h,
return retval;
}
#ifdef keep_as_example
/*! Registered Upgrade callback function
* @param[in] h Clicon handle
* @param[in] xn XML tree to be updated
@ -259,22 +258,44 @@ example_statedata(clicon_handle h,
* @retval 1 OK
* @retval 0 Invalid
* @retval -1 Error
* @see clicon_upgrade_cb
*/
static int
upgrade_all(clicon_handle h,
cxobj *xn,
char *modname,
char *modns,
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;
size_t vlen;
int i;
/* 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<vlen; i++){
xc = vec[i];
clicon_debug(1, "%s update %s", __FUNCTION__, xml_name(xc));
}
ok:
retval = 1;
done:
if (vec)
free(vec);
return retval;
}
#endif
/*! Plugin state reset. Add xml or set state in backend machine.
* Called in each backend plugin. plugin_reset is called after all plugins
@ -444,12 +465,9 @@ 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)
/* General purpose upgrade callback */
if (upgrade_callback_register(h, 0?upgrade_all:xml_changelog_upgrade,
NULL, 0, 0, NULL) < 0)
goto done;

View file

@ -90,7 +90,7 @@
#include <clixon/clixon_json.h>
#include <clixon/clixon_netconf_lib.h>
#include <clixon/clixon_nacm.h>
#include <clixon/clixon_yang_changelog.h>
#include <clixon/clixon_xml_changelog.h>
/*
* Global variables generated by Makefile

View file

@ -210,8 +210,8 @@ 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);
#endif /* _CLIXON_OPTIONS_H_ */

View file

@ -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,
@ -246,8 +244,8 @@ int rpc_callback_delete_all(void);
int rpc_callback_call(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg);
/* upgrade callback API */
int upgrade_callback_register(clicon_handle h, clicon_upgrade_cb cb, void *arg, char *name, char *namespace, uint32_t from, uint32_t to);
int upgrade_callback_register(clicon_handle h, clicon_upgrade_cb cb, char *namespace, uint32_t from, uint32_t to, void *arg);
int upgrade_callback_delete_all(void);
int upgrade_callback_call(clicon_handle h, cxobj *xt, char *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_ */

View file

@ -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 */

View file

@ -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;
/*

View file

@ -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 \

View file

@ -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:

View file

@ -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,12 @@ 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;
}

View file

@ -542,10 +542,10 @@ typedef struct {
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 ??? */
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 */
uint32_t uc_to; /* Module revision (to) in YYYYMMDD format */
} upgrade_callback_t;
/* List of rpc callback entries XXX hang on handle */
@ -556,10 +556,9 @@ static upgrade_callback_t *upgrade_cb_list = NULL;
* @param[in] h clicon handle
* @param[in] cb Callback called
* @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
@ -567,11 +566,10 @@ static upgrade_callback_t *upgrade_cb_list = NULL;
int
upgrade_callback_register(clicon_handle h,
clicon_upgrade_cb cb,
void *arg,
char *name,
char *namespace,
uint32_t revision,
uint32_t from,
uint32_t to)
void *arg)
{
upgrade_callback_t *uc;
@ -582,18 +580,14 @@ upgrade_callback_register(clicon_handle h,
memset(uc, 0, sizeof(*uc));
uc->uc_callback = cb;
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 +604,6 @@ upgrade_callback_delete_all(void)
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 +614,7 @@ upgrade_callback_delete_all(void)
/*! 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
@ -636,8 +628,7 @@ upgrade_callback_delete_all(void)
int
upgrade_callback_call(clicon_handle h,
cxobj *xt,
char *modname,
char *modns,
char *namespace,
uint32_t from,
uint32_t to,
cbuf *cbret)
@ -659,11 +650,11 @@ 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)

View file

@ -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; i<xparent->x_childvec_len; i++){
xn = xparent->x_childvec[i];
if (xn == NULL)
@ -1612,8 +1615,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:

View file

@ -0,0 +1,367 @@
/*
*
***** 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* 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"
/*! Perform a changelog operation
* @param[in] h Clicon handle
* @param[in] xi Changelog item
* @param[in] xn XML to upgrade
* @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 *xptarget; /* xpath to target-node */
char *xplocation; /* xpath to location-node (move) */
char *ftransform; /* transform string format (modify, create) */
cxobj *xtrg; /* xml target node */
cxobj *xloc; /* xml location node */
cxobj *xnew = NULL;
cxobj *x;
if ((op = xml_find_body(xi, "change-operation")) == NULL)
goto ok;
if ((xptarget = xml_find_body(xi, "target-node")) == NULL)
goto ok;
/* target node (if any) */
if ((xtrg = xpath_first(xt, "%s", xptarget)) == NULL)
goto fail;
// fprintf(stderr, "%s %s %s\n", __FUNCTION__, op, xml_name(xt));
xplocation = xml_find_body(xi, "location-node");
ftransform = xml_find_body(xi, "transform");
if (strcmp(op, "insert") == 0){
/* create a new node by parsing fttransform string and insert it at
target */
if (ftransform == NULL)
goto fail;
if (xml_parse_va(&xtrg, NULL, "%s", ftransform) < 0)
goto done;
}
else if (strcmp(op, "delete") == 0){
/* delete target */
if (xml_purge(xtrg) < 0)
goto done;
}
else if (strcmp(op, "move") == 0){
/* Move target node to location */
if ((xloc = xpath_first(xt, "%s", xplocation)) == NULL)
goto fail;
if (xml_addsub(xloc, xtrg) < 0)
goto done;
}
else if (strcmp(op, "replace") == 0){
/* create a new node by parsing fttransform string and insert it at
target */
if (ftransform == NULL)
goto fail;
/* replace: remove all children of target */
while ((x = xml_child_i(xtrg, 0)) != NULL)
if (xml_purge(x) < 0)
goto done;
/* Parse the new node */
if (xml_parse_va(&xnew, NULL, "%s", ftransform) < 0)
goto done;
if (xml_rootchild(xnew, 0, &xnew) < 0)
goto done;
/* Copy old to new */
if (xml_copy(xnew, xtrg) < 0)
goto done;
if (xml_purge(xnew) < 0)
goto done;
}
ok:
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Iterate through one changelog item
* @param[in] h Clicon handle
* @param[in] xt Changelog list
* @param[in] xn XML to upgrade
*/
static int
changelog_iterate(clicon_handle h,
cxobj *xt,
cxobj *xch)
{
int retval = -1;
cxobj **vec = NULL;
size_t veclen;
int ret;
int i;
if (xpath_vec(xch, "change-log", &vec, &veclen) < 0)
goto done;
/* Iterate through changelog items */
for (i=0; i<veclen; i++){
if ((ret = changelog_op(h, xt, vec[i])) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Automatic upgrade using changelog
* @param[in] h Clicon handle
* @param[in] xt Top-level XML tree to be updated (includes other ns as well)
* @param[in] namespace 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()
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @retval 1 OK
* @retval 0 Invalid
* @retval -1 Error
* @see upgrade_callback_register where this function should be registered
*/
int
xml_changelog_upgrade(clicon_handle h,
cxobj *xt,
char *namespace,
uint32_t from,
uint32_t to,
void *arg,
cbuf *cbret)
{
int retval = -1;
cxobj *xchlog; /* changelog */
cxobj **vec = NULL;
cxobj *xch;
size_t veclen;
char *b;
int ret;
int i;
uint32_t f;
uint32_t t;
/* Check if changelog enabled */
if (!clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
goto ok;
/* Get changelog */
if ((xchlog = clicon_xml_changelog_get(h)) == NULL)
goto ok;
/* Iterate and find relevant changelog entries in the interval:
* - find all changelogs in the interval: [from, to]
* - note it t=0 then no changelog is applied
*/
if (xpath_vec(xchlog, "module[namespace=\"%s\"]",
&vec, &veclen, namespace) < 0)
goto done;
/* Get all changelogs in the interval [from,to]*/
for (i=0; i<veclen; i++){
xch = vec[i];
f = t = 0;
if ((b = xml_find_body(xch, "revfrom")) != NULL)
if (ys_parse_date_arg(b, &f) < 0)
goto done;
if ((b = xml_find_body(xch, "revision")) != NULL)
if (ys_parse_date_arg(b, &t) < 0)
goto done;
if ((f && from>f) || to<t)
continue;
if ((ret = changelog_iterate(h, xt, xch)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Initialize module revision. read changelog, etc
*/
int
clixon_xml_changelog_init(clicon_handle h)
{
int retval = -1;
char *filename;
int fd = -1;
cxobj *xt = NULL;
yang_spec *yspec;
cbuf *cbret = NULL;
int ret;
yspec = clicon_dbspec_yang(h);
if ((filename = clicon_option_str(h, "CLICON_XML_CHANGELOG_FILE")) != NULL){
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
if (xml_parse_file(fd, NULL, yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((ret = xml_yang_validate_all(xt, cbret)) < 0)
goto done;
if (ret==1 && (ret = xml_yang_validate_add(xt, cbret)) < 0)
goto done;
if (ret == 0){ /* validation failed */
clicon_err(OE_YANG, 0, "validation failed: %s", cbuf_get(cbret));
goto done;
}
if (clicon_xml_changelog_set(h, xt) < 0)
goto done;
xt = NULL;
}
retval = 0;
done:
if (fd != -1)
close(fd);
if (xt)
xml_free(xt);
if (cbret)
cbuf_free(cbret);
return retval;
}
/*! Given a top-level XML tree and a namespace, return a vector of matching XML nodes
* @param[in] h Clicon handle
* @param[in] xt Top-level XML tree, with children marked with namespaces
* @param[in] namespace The namespace to select
* @param[out] vecp Vector containining XML nodes w namespace. Null-terminated.
* @param[out] veclenp Length of vector
* @note Need to free vec after use with free()
* Example
* xt ::= <config><a xmlns="urn:example:a"/><aaa xmlns="urn:example:a"/><a xmlns="urn:example:b"/></config
* namespace ::= urn:example:a
* out:
* vec ::= [<a xmlns="urn:example:a"/>, <aaa xmlns="urn:example:a"/>, 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;
}

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <fcntl.h>
/* cligen */
#include <cligen/cligen.h>
/* 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
<index>0001</index>
<change-operation>create</change-operation>
<data-definition>
<target-node>
/a:system/a:y;
</target-node>
</data-definition>
*/
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<veclen; i++)
if (upgrade_op(vec[i]) < 0)
goto done;
retval = 0;
done:
if (vec)
free(vec);
return retval;
}
#endif
/*! Automatic upgrade using changelog
* @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] 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()
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @retval 1 OK
* @retval 0 Invalid
* @retval -1 Error
*/
int
yang_changelog_upgrade(clicon_handle h,
cxobj *xn,
char *modname,
char *modns,
uint32_t from,
uint32_t to,
void *arg,
cbuf *cbret)
{
// cxobj *xchlog; /* changelog */
// cxobj **vec = NULL;
// size_t veclen;
if (!clicon_option_bool(h, "CLICON_YANG_CHANGELOG"))
goto ok;
/* Get changelog */
// xchlog = clicon_yang_changelog_get(h);
/* Get changelog entries for module between from and to
* (if to=0 we may not know name, need to use namespace)
*/
#if 0
if (xpath_vec(xchlog, "module[name=\"%s\" and revision=\"%s\"]/revision-change-log",
&vec, &veclen, modname, mrev) < 0)
goto done;
#endif
ok:
return 1;
}
/*! Initialize module revision. read changelog, etc
*/
int
clixon_yang_changelog_init(clicon_handle h)
{
int retval = -1;
char *filename;
int fd = -1;
cxobj *xt = NULL;
yang_spec *yspec;
cbuf *cbret = NULL;
int ret;
yspec = clicon_dbspec_yang(h);
if ((filename = clicon_option_str(h, "CLICON_YANG_CHANGELOG_FILE")) != NULL){
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
}
if (xml_parse_file(fd, NULL, yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((ret = xml_yang_validate_all(xt, cbret)) < 0)
goto done;
if (ret==1 && (ret = xml_yang_validate_add(xt, cbret)) < 0)
goto done;
if (ret == 0){ /* validation failed */
clicon_err(OE_YANG, 0, "validation failed: %s", cbuf_get(cbret));
goto done;
}
if (clicon_yang_changelog_set(h, xt) < 0)
goto done;
xt = NULL;
}
retval = 0;
done:
if (fd != -1)
close(fd);
if (xt)
xml_free(xt);
if (cbret)
cbuf_free(cbret);
return retval;
}

View file

@ -94,9 +94,9 @@ modstate_diff_free(modstate_diff_t *md)
if (md == NULL)
return 0;
if (md->md_del)
free(md->md_del);
xml_free(md->md_del);
if (md->md_mod)
free(md->md_mod);
xml_free(md->md_mod);
free(md);
return 0;
}
@ -375,12 +375,72 @@ yang_modules_state_get(clicon_handle h,
return retval;
}
/*! For single module state with namespace, get revisions and send upgrade callbacks
* @param[in] h Clicon handle
* @param[in] xt Top-level XML tree to be updated (includes other ns as well)
* @param[in] xs XML module state (for one yang module)
* @param[in] xvec Help vector where to store XML child nodes (??)
* @param[in] xlen Length of xvec
* @param[in] ns0 Namespace of module state we are looking for
* @param[in] deleted If set, dont look for system yang module and "to" rev
* @param[out] cbret Netconf error message if invalid
* @retval 1 OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
*/
static int
mod_ns_upgrade(clicon_handle h,
cxobj *xt,
cxobj *xs,
char *ns,
int deleted,
cbuf *cbret)
{
int retval = -1;
char *b; /* string body */
yang_stmt *ymod;
yang_stmt *yrev;
uint32_t from = 0;
uint32_t to = 0;
int ret;
yang_spec *yspec;
/* Make upgrade callback for this XML, specifying the module
* namespace, from and to revision.
*/
if ((b = xml_find_body(xs, "revision")) != NULL) /* Module revision */
if (ys_parse_date_arg(b, &from) < 0)
goto done;
if (deleted)
to = 0;
else { /* Look up system module (alt send it via argument) */
yspec = clicon_dbspec_yang(h);
if ((ymod = yang_find_module_by_namespace(yspec, ns)) == 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;
}
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;
char *ns; /* 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;
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 */
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;
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)
/* 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;

View file

@ -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,6 +16,7 @@ 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
@ -69,7 +70,7 @@ cat <<EOF > $exa20y
"foo.";
revision 2017-12-20 {
description "Create y, delete x, modify host-name, move z";
description "Create y, delete x, replace host-name, move z";
}
revision 2017-12-01 {
description "Initial revision.";
@ -81,7 +82,7 @@ cat <<EOF > $exa20y
}
leaf host-name {
type string;
description "modify type";
description "replace";
}
leaf y {
type string;
@ -97,6 +98,24 @@ cat <<EOF > $exa20y
}
EOF
# 3.2.1 and 4.1 example-a revision 2017-12-20
cat <<EOF > $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
# Create failsafe db
cat <<EOF > $dir/failsafe_db
<config>
@ -129,12 +148,18 @@ cat <<EOF > $dir/startup_db
<x>remove me</x>
<z>move me</z>
</system>
<alt xmlns="urn:example:a">
</alt>
<system-b xmlns="urn:example:b">
<b>Obsolete</b>
</system-b>
</config>
EOF
# Wanted new XML
XML='<system xmlns="urn:example:a"><a>dont change me</a><host-name>i am modified</host-name><y>created</y></system><alt xmlns="urn:example:a"><z>move me</z></alt>'
# Create configuration
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
@ -147,8 +172,8 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_XMLDB_MODSTATE>true</CLICON_XMLDB_MODSTATE>
<CLICON_MODULE_REVISION>true</CLICON_MODULE_REVISION>
<CLICON_MODULE_REVISION_CHANGELOG>$changelog</CLICON_MODULE_REVISION_CHANGELOG>
<CLICON_XML_CHANGELOG>true</CLICON_XML_CHANGELOG>
<CLICON_XML_CHANGELOG_FILE>$changelog</CLICON_XML_CHANGELOG_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
@ -157,52 +182,44 @@ EOF
# Changelog of example-a:
cat <<EOF > $changelog
<yang-modules xmlns="http://clicon.org/yang-changelog">
<yang-modules xmlns="http://clicon.org/xml-changelog">
<module>
<name>example-b</name>
<revision>2017-12-01</revision>
<!--obsolete-->
<namespace>urn:example:b</namespace>
<revfrom>2017-12-01</revfrom>
<revision>2017-12-20</revision>
<change-log>
<index>0001</index>
<change-operation>delete</change-operation>
<target-node>/b:system-b</target-node>
</change-log>
</module>
<module>
<name>example-a</name>
<namespace>urn:example:a</namespace>
<revfrom>2017-12-01</revfrom>
<revision>2017-12-20</revision>
<backward-compatible>true</backward-compatible>
<revision-change-log>
<change-log>
<index>0001</index>
<change-operation>create</change-operation>
<data-definition>
<target-node>
/a:system/a:y;
</target-node>
</data-definition>
</revision-change-log>
<revision-change-log>
<change-operation>insert</change-operation>
<target-node>/a:system</target-node>
<transform>&lt;y&gt;created&lt;/y&gt;</transform>
</change-log>
<change-log>
<index>0002</index>
<change-operation>delete</change-operation>
<data-definition>
<target-node>
/a:system/a:x;
</target-node>
</data-definition>
</revision-change-log>
<revision-change-log>
<target-node>/a:system/a:x</target-node>
</change-log>
<change-log>
<index>0003</index>
<change-operation>modify</change-operation>
<data-definition>
<target-node>
/a:system/a:host-name;
</target-node>
</data-definition>
</revision-change-log>
<revision-change-log>
<change-operation>replace</change-operation>
<target-node>/a:system/a:host-name</target-node>
<transform>&lt;host-name&gt;i am modified&lt;/host-name&gt;</transform>
</change-log>
<change-log>
<index>0004</index>
<change-operation>move</change-operation>
<data-definition>
<target-node>
/a:system/a:z;
</target-node>
</data-definition>
</revision-change-log>
<target-node>/a:system/a:z</target-node>
<location-node>/a:alt</location-node>
</change-log>
</module>
</yang-modules>
EOF
@ -236,7 +253,7 @@ sleep $RCWAIT
new "Check failsafe (work in progress)"
new "Check running db content"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><system xmlns="urn:example:a"><a>Failsafe</a></system></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>' "^<rpc-reply><data>$XML</data></rpc-reply>]]>]]>$"
new "Kill restconf daemon"
stop_restconf

View file

@ -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

View file

@ -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";

View file

@ -0,0 +1,134 @@
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 <olof@hagsand.se>";
description
"This is experimental XML changelog module with several influences:
1) draft-wang-netmod-module-revision-management-01, by:
Qin Wu <mailto:bill.wu@huawei.com>
Zitao Wang <mailto:wangzitao@huawei.com>
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 target node (NYI)";
}
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";
}
enum insert {
description "Create new data nodes and insert under an existing node";
}
enum delete {
description "Delete the target node";
}
enum move {
description "Move the target node(Added)";
}
enum wrap {
description "Wraps elements with additional elements(NYI)";
}
enum reorder {
description "Changes the order of elements (NYI)";
}
}
}
container yang-modules {
config false;
list module {
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 change-log {
description
"List for module revision change log";
key "index";
leaf index {
type uint32;
description
"Index for module change log";
}
leaf change-operation {
type operation_type;
mandatory true;
description
"This leaf indicate the change operation, such as create, move, delete, modify, etc.";
}
leaf target-node {
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 location-node {
description
"If op is move, this denotes the destination";
type yang:xpath1.0;
}
leaf transform {
description
"If op is modify or create, this denotes how to
transform the XML encoding.
Special value %s for the original value.";
type string;
}
}
}
}
}

View file

@ -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 <olof@hagsand.se>";
description
"This is experimentalYANG changelog derived from:
draft-wang-netmod-module-revision-management-01
with the following contacts and references:
WG Web: <https://datatracker.ietf.org/wg/netmod/>
WG List: <mailto:netmod@ietf.org>
Author: Qin Wu
<mailto:bill.wu@huawei.com>
Zitao Wang
<mailto:wangzitao@huawei.com>";
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.";
}
}
}