Fixed: [Defaults in choice does not work properly](https://github.com/clicon/clixon/issues/390)

C: Added new file clixon_xml_default.[ch] and moved all default handling there
This commit is contained in:
Olof hagsand 2022-12-22 09:15:10 +01:00
parent 7e92f67f4f
commit ffe918dd0e
16 changed files with 1015 additions and 667 deletions

View file

@ -41,6 +41,18 @@
## 6.1.0
Expected: beginning of 2023
### API changes on existing protocol/config features
Users may have to change how they access the system
* With-defaults default retrieval mode has changed from `REPORT-ALL` to `EXPLICIT`
* This means that all get operations without `with-defaults` parameter do no longer
return implicit default values, only explicitly set values.
* Applies to NETCONF `<get>`, `<get-config>` and RESTCONF `GET`
* To keep backward-compatible behavior, define option `NETCONF_DEFAULT_RETRIEVAL_REPORT_ALL` in
include/clixon_custom.h
* Alternatively, change all get operation to include with-defaults parameter `report-all`
### C/CLI-API changes on existing features
Developers may need to change their code
@ -61,7 +73,8 @@ Developers may need to change their code
### Corrected Bugs
* Fixed [Netconf monitoring](https://github.com/clicon/clixon/issues/370)
* Fixed: [Defaults in choice does not work properly](https://github.com/clicon/clixon/issues/390)
* Fixed: [Netconf monitoring](https://github.com/clicon/clixon/issues/370)
- Announce module capability
- Return origin Yang file in get-schema

View file

@ -94,6 +94,7 @@ extern "C" {
#include <clixon/clixon_data.h>
#include <clixon/clixon_regex.h>
#include <clixon/clixon_path.h>
#include <clixon/clixon_xml_default.h>
#include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_bind.h>
#include <clixon/clixon_xml_io.h>

View file

@ -0,0 +1,59 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
*
* XML default values
*/
#ifndef _CLIXON_XML_DEFAULT_H_
#define _CLIXON_XML_DEFAULT_H_
/*
* Types
*/
/* Declared in clixon_yang_internal */
typedef enum yang_class yang_class;
/*
* Prototypes
*/
int xml_default_recurse(cxobj *xn, int state);
int xml_global_defaults(clicon_handle h, cxobj *xn, cvec *nsc, const char *xpath, yang_stmt *yspec, int state);
int xml_defaults_nopresence(cxobj *xn, int purge);
int xml_add_default_tag(cxobj *x, uint16_t flags);
int xml_flag_state_default_value(cxobj *x, uint16_t flag);
int xml_flag_default_value(cxobj *x, uint16_t flag);
#endif /* _CLIXON_XML_DEFAULT_H_ */

View file

@ -61,9 +61,6 @@ 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 xml_tree_prune_flags(cxobj *xt, int flags, int mask);
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_default_recurse(cxobj *xn, int state);
int xml_global_defaults(clicon_handle h, cxobj *xn, cvec *nsc, const char *xpath, yang_stmt *yspec, int state);
int xml_defaults_nopresence(cxobj *xn, int purge);
int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, cxobj **xerr);
int xml2xpath(cxobj *x, cvec *nsc, char **xpath);
@ -79,8 +76,5 @@ int yang_xml_mandatory(cxobj *xt, yang_stmt *ys);
int xml_rpc_isaction(cxobj *xn);
int xml_find_action(cxobj *xn, int top, cxobj **xap);
int purge_tagged_nodes(cxobj *xn, char *ns, char *name, char *value, int keepnode);
int xml_add_default_tag(cxobj *x, uint16_t flags);
int xml_flag_state_default_value(cxobj *x, uint16_t flag);
int xml_flag_default_value(cxobj *x, uint16_t flag);
#endif /* _CLIXON_XML_MAP_H_ */

View file

@ -66,5 +66,6 @@ int xml2ns_recurse(cxobj *x);
int xmlns_set(cxobj *x, char *prefix, char *ns);
int xmlns_set_all(cxobj *x, cvec *nsc);
int xml2prefix(cxobj *xn, char *ns, char **prefixp);
int xml_add_namespace(cxobj *x, cxobj *xp, char *prefix, char *ns);
#endif /* _CLIXON_XML_NSCTX_H */

View file

@ -257,6 +257,7 @@ char *yang_find_mynamespace(yang_stmt *ys);
int yang_find_prefix_by_namespace(yang_stmt *ys, char *ns, char **prefix);
int yang_find_namespace_by_prefix(yang_stmt *ys, char *prefix, char **ns);
yang_stmt *yang_myroot(yang_stmt *ys);
int choice_case_get(yang_stmt *yc, yang_stmt **ycase, yang_stmt **ychoice);
yang_stmt *yang_choice(yang_stmt *y);
int yang_order(yang_stmt *y);
int yang_print_cb(FILE *f, yang_stmt *yn, clicon_output_cb *fn);

View file

@ -81,7 +81,7 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$
SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \
clixon_xml.c clixon_xml_io.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c \
clixon_xml_bind.c clixon_json.c clixon_proc.c \
clixon_xml_default.c clixon_xml_bind.c clixon_json.c clixon_proc.c \
clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_netconf_monitoring.c \
clixon_yang_parse_lib.c clixon_yang_sub_parse.c \
clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \

View file

@ -78,6 +78,7 @@
#include "clixon_yang_module.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_xml_map.h"
#include "clixon_xml_default.h"
#include "clixon_xml_io.h"
#include "clixon_xml_nsctx.h"
#include "clixon_datastore.h"

View file

@ -77,6 +77,7 @@
#include "clixon_yang_module.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_io.h"
#include "clixon_xml_default.h"
#include "clixon_xml_map.h"
#include "clixon_datastore.h"
#include "clixon_datastore_write.h"
@ -336,30 +337,6 @@ check_when_condition(cxobj *x0p,
goto done;
}
/*! Get cloxest yang case and choice, if any
*/
static int
choice_case_get(yang_stmt *yc,
yang_stmt **ycase,
yang_stmt **ychoice)
{
yang_stmt *yp;
if ((yp = yang_parent_get(yc)) == NULL)
return 0;
if (yang_keyword_get(yp) == Y_CASE){
*ycase = yp;
*ychoice = yang_parent_get(yp);
return 1;
}
else if (yang_keyword_get(yp) == Y_CHOICE){
*ycase = NULL;
*ychoice = yp;
return 1;
}
return 0;
}
/*! Check if x0/y0 is part of other choice/case than y1 recursively , if so purge
* @retval 0 No, y0 it is not in other case than y1
* @retval 1 yes, y0 is in other case than y1

View file

@ -80,7 +80,7 @@
#include "clixon_xml_nsctx.h"
#include "clixon_xml_io.h"
#include "clixon_validate.h"
#include "clixon_xml_map.h"
#include "clixon_xml_default.h"
/* Mapping between Clicon startup modes string <--> constants,
see clixon-config.yang type startup_mode */

View file

@ -76,6 +76,7 @@
#include "clixon_yang_module.h"
#include "clixon_yang_type.h"
#include "clixon_xml_map.h"
#include "clixon_xml_default.h"
#include "clixon_xml_bind.h"
#include "clixon_validate_minmax.h"
#include "clixon_validate.h"

View file

@ -0,0 +1,703 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
*
* XML default values
*/
#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 <ctype.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_string.h"
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_data.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
#include "clixon_xml_default.h"
/* Forward */
static int xml_default(yang_stmt *yt, cxobj *xt, int state);
/*!
*/
static int
xml_default_create1(yang_stmt *y,
cxobj *xt,
cxobj **xcp)
{
int retval = -1;
char *namespace;
char *prefix;
int ret;
cxobj *xc = NULL;
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xc, y);
/* assign right prefix */
if ((namespace = yang_find_mynamespace(y)) != NULL){
prefix = NULL;
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
goto done;
if (ret){ /* Namespace found, prefix returned in prefix */
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
else{ /* Namespace does not exist in target, must add it w xmlns attr.
use source prefix */
if (xml_add_namespace(xc, xc, prefix, namespace) < 0)
goto done;
/* Add prefix to x, if any */
if (prefix && xml_prefix_set(xc, prefix) < 0)
goto done;
}
}
if (xml_addsub(xt, xc) < 0)
goto done;
*xcp = xc;
retval = 0;
done:
return retval;
}
/*! Create leaf from default value
*
* @param[in] y Yang spec
* @param[in] xt XML tree
* @param[in] top Use default namespace (if you create xmlns statement)
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default_create(yang_stmt *y,
cxobj *xt,
int top)
{
int retval = -1;
cxobj *xc = NULL;
cxobj *xb;
char *str;
cg_var *cv;
if (xml_default_create1(y, xt, &xc) < 0)
goto done;
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL){
clicon_err(OE_UNIX, ENOENT, "No yang cv of %s", yang_argument_get(y));
goto done;
}
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
retval = 0;
done:
return retval;
}
/*! Traverse a choice
* From RFC7950 Sec 7.9.3
* 1. Default case, the default if no child nodes from any of the choice's cases exist
* 2. Default for child nodes under a case are only used if one of the nodes under that case
* is present
*/
static int
xml_default_choice(yang_stmt *yc,
cxobj *xt,
int state)
{
int retval = -1;
cxobj *x = NULL;
cxobj *x0 = NULL;
yang_stmt *y;
yang_stmt *ych = NULL;
yang_stmt *yca = NULL;
yang_stmt *ydef;
clicon_debug(1, "%s", __FUNCTION__);
/* 1. Is there a default case and no child under this choice?
*/
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) == NULL)
continue;
/* Check if this child is a child of yc */
yca = ych = NULL;
if (choice_case_get(y, &yca, &ych) == 1 &&
ych == yc){
x0 = x;
break;
}
}
if (x0 == NULL){ /* case 1: no child nodes of any of the choice's cases */
if ((ydef = yang_find(yc, Y_DEFAULT, NULL)) != NULL)
yca = yang_find(yc, Y_CASE, yang_argument_get(ydef));
else
yca = NULL;
}
if (yca)
if (xml_default(yca, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Try to see if intermediate nodes are necessary for default values, create if so
*
* @param[in] yt Yang container (no-presence)
* @param[in] state Set if global state, otherwise config
* @param[out] createp Need to create XML container
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_nopresence_try(yang_stmt *yt,
int state,
int *createp)
{
int retval = -1;
yang_stmt *y;
yang_stmt *ydef;
if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
clicon_err(OE_XML, EINVAL, "yt argument is not container");
goto done;
}
*createp = 0;
y = NULL;
while ((y = yn_each(yt, y)) != NULL) {
switch (yang_keyword_get(y)){
case Y_LEAF:
/* Default value exists */
if (!cv_flag(yang_cv_get(y), V_UNSET)){
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(y))
;
else
/* Need to create container */
*createp = 1;
goto ok;
}
break;
case Y_CONTAINER:
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
/* If this is non-presence, (and it does not exist in xt) call recursively
* and create nodes if any default value exist first. Then continue and populate?
*/
if (xml_nopresence_try(y, state, createp) < 0)
goto done;
if (*createp)
goto ok;
}
break;
case Y_CHOICE:
if ((ydef = yang_find(y, Y_DEFAULT, NULL)) != NULL &&
yang_find(y, Y_CASE, yang_argument_get(ydef)))
*createp = 1;
break;
default:
break;
} /* switch */
}
ok:
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on (children of) one single xml node
*
* Not recursive, except in one case with one or several non-presence containers, in which case
* XML containers may be created to host default values. That code may be a little too recursive.
* @param[in] yt Yang spec, usually spec of xt but always (eg Y_CASE)
* @param[in] xt XML tree (with yt as spec of xt, informally)
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* XXX If state, should not add config defaults
* if (state && yang_config(yc))
*/
static int
xml_default(yang_stmt *yt,
cxobj *xt,
int state)
{
int retval = -1;
yang_stmt *yc;
cxobj *xc;
int top = 0; /* Top symbol (set default namespace) */
int create = 0;
char *xpath;
int nr = 0;
int hit = 0;
cg_var *cv;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
goto done;
}
switch (yang_keyword_get(yt)){
case Y_MODULE:
case Y_SUBMODULE:
top++;
case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
case Y_LIST:
case Y_INPUT:
case Y_OUTPUT:
case Y_CASE:
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
/* If config parameter and local is config false */
if (!state && !yang_config(yc))
continue;
switch (yang_keyword_get(yc)){
case Y_LEAF:
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(yc))
break;
if ((cv = yang_cv_get(yc)) == NULL){
clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should",
yang_argument_get(yc));
goto done;
}
if (!cv_flag(cv, V_UNSET)){ /* Default value exists */
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
goto done;
xml_sort(xt);
}
}
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?
*/
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such container exist, recursively try if needed */
if (xml_nopresence_try(yc, state, &create) < 0)
goto done;
if (create){
/* Retval shows there is a default value need to create the
* container */
if (xml_default_create1(yc, xt, &xc) < 0)
goto done;
xml_sort(xt);
/* Then call it recursively */
if (xml_default(yc, xc, state) < 0)
goto done;
}
}
}
break;
case Y_CHOICE:{
if (xml_default_choice(yc, xt, state) < 0)
goto done;
}
default:
break;
}
}
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Recursively fill in default values in an XML tree
* @param[in] xt XML tree
* @param[in] state If set expand defaults also for state data, otherwise only config
* @retval 0 OK
* @retval -1 Error
* @see xml_global_defaults
*/
int
xml_default_recurse(cxobj *xn,
int state)
{
int retval = -1;
yang_stmt *yn;
cxobj *x;
yang_stmt *y;
if ((yn = (yang_stmt*)xml_spec(xn)) != NULL)
if (xml_default(yn, xn, state) < 0)
goto done;
x = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((y = (yang_stmt*)xml_spec(x)) != NULL){
if (!state && !yang_config(y))
continue;
}
if (xml_default_recurse(x, state) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] xt XML tree
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state p Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_global_defaults_create(cxobj *xt,
yang_stmt *yspec,
int state)
{
int retval = -1;
yang_stmt *ymod = NULL;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
goto done;
}
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (xml_default(ymod, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] h Clixon handle
* @param[in] xt XML tree, assume already filtered with xpath
* @param[in] xpath Filter global defaults with this and merge with xt
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* Uses cache?
* @see xml_default_recurse
*/
int
xml_global_defaults(clicon_handle h,
cxobj *xt,
cvec *nsc,
const char *xpath,
yang_stmt *yspec,
int state)
{
int retval = -1;
db_elmnt de0 = {0,};
db_elmnt *de = NULL;
cxobj *xcache = NULL;
cxobj *xpart = NULL;
cxobj **xvec = NULL;
size_t xlen;
int i;
cxobj *x0;
int ret;
char *key;
/* Use different keys for config and state */
key = state ? "global-defaults-state" : "global-defaults-config";
/* First get or compute global xml tree cache */
if ((de = clicon_db_elmnt_get(h, key)) == NULL){
/* Create it */
if ((xcache = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_global_defaults_create(xcache, yspec, state) < 0)
goto done;
de0.de_xml = xcache;
clicon_db_elmnt_set(h, key, &de0);
}
else
xcache = de->de_xml;
/* Here xcache has all global defaults. Now find the matching nodes
* XXX: nsc as 2nd argument
*/
if (xpath_vec(xcache, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* Iterate through match vector
* For every node found in x0, mark the tree up to t1
*/
for (i=0; i<xlen; i++){
x0 = xvec[i];
xml_flag_set(x0, XML_FLAG_MARK);
xml_apply_ancestor(x0, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
/* Create a new tree and copy over the parts from the cache that matches xpath */
if ((xpart = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_copy_marked(xcache, xpart) < 0) /* config */
goto done;
if (xml_apply(xcache, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
if (xml_apply(xpart, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
/* Merge global pruned tree with xt */
if ((ret = xml_merge(xt, xpart, yspec, NULL)) < 1) /* XXX reason */
goto done;
retval = 0;
done:
if (xpart)
xml_free(xpart);
if (xvec)
free(xvec);
return retval;
}
/*! Recursively find empty nopresence containers and default leaves, optionally purge
*
* @param[in] xn XML tree
* @param[in] purge 0: Dont remove any nodes
* 1: Remove config sub-nodes that are empty non-presence container or default leaf
* 2: Remove all sub-nodes that are empty non-presence container or default leaf
* @retval 1 Node is an (recursive) empty non-presence container or default leaf
* @retval 0 Other node
* @retval -1 Error
* @note xn is not itself removed if purge
* @note for purge=1 are removed only if config or no yang spec(!)
*/
int
xml_defaults_nopresence(cxobj *xn,
int purge)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
yang_stmt *yn;
yang_stmt *y;
int rmx = 0; /* If set, remove this xn */
int ret;
enum rfc_6020 keyw;
int config = 1;
if ((yn = xml_spec(xn)) != NULL){
keyw = yang_keyword_get(yn);
if (keyw == Y_CONTAINER &&
yang_find(yn, Y_PRESENCE, NULL) == NULL)
rmx = 1;
else if (keyw == Y_LEAF &&
xml_flag(xn, XML_FLAG_DEFAULT))
rmx = 1;
config = yang_config_ancestor(yn);
}
/* Loop thru children */
x = NULL;
xprev = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((ret = xml_defaults_nopresence(x, purge)) < 0)
goto done;
if (ret == 1){
switch (purge){
case 1: /* config nodes only */
if (!config)
break;
if ((y = xml_spec(x)) != NULL &&
!yang_config(y))
break;
/* fall thru */
case 2: /* purge all nodes */
if (xml_purge(x) < 0)
goto done;
x = xprev;
break;
default:
break;
}
}
else if (rmx)
/* May switch an empty non-presence container (rmx=1) to non-empty non-presence container (rmx=0) */
rmx = 0;
}
retval = rmx;
done:
return retval;
}
/*! Add default attribute to node with default value.
*
* Used in with-default code for report-all-tagged
* @param[in] x XML node
* @param[in] flags Flags indicatiing default nodes
* @retval 0 OK
* @retval -1 Error
*/
int
xml_add_default_tag(cxobj *x,
uint16_t flags)
{
int retval = -1;
cxobj *xattr;
if (xml_flag(x, flags)) {
/* set default attribute */
if ((xattr = xml_new("default", x, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xattr, "true") < 0)
goto done;
if (xml_prefix_set(xattr, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Set flag on node having schema default value. (non-config)
*
* Used in with-default code for trim/report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_default_value for config default value
*/
int
xml_flag_state_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if (yang_config_ancestor(y) == 1)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}
/*! Set flag on node having schema default value. (config)
*
* Used in with-default code for trim and report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_state_default_value for non-config default value
*/
int
xml_flag_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}

View file

@ -605,45 +605,6 @@ xml_tree_prune_flags(cxobj *xt,
return retval;
}
/*! Add prefix:namespace pair to xml node, set cache, etc
* @param[in] x XML node whose namespace should change
* @param[in] xp XML node where namespace attribute should be declared (can be same)
* @param[in] prefix1 Use this prefix
* @param[in] namespace Use this namespace
* @note x and xp must be different if x is an attribute and may be different otherwise
*/
static int
add_namespace(cxobj *x,
cxobj *xp,
char *prefix,
char *namespace)
{
int retval = -1;
cxobj *xa = NULL;
/* Add binding to x1p. We add to parent due to heurestics, so we dont
* end up in adding it to large number of siblings
*/
if (nscache_set(x, prefix, namespace) < 0)
goto done;
/* Create xmlns attribute to x1p/x1 XXX same code v */
if (prefix){
if ((xa = xml_new(prefix, xp, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", xp, CX_ATTR)) == NULL)
goto done;
}
if (xml_value_set(xa, namespace) < 0)
goto done;
xml_sort(xp); /* Ensure attr is first / XXX xml_insert? */
retval = 0;
done:
return retval;
}
/*! Change namespace of XML node
*
@ -682,7 +643,7 @@ xml_namespace_change(cxobj *x,
xp = x;
else
xp = xml_parent(x);
if (add_namespace(x, xp, prefix, ns) < 0)
if (xml_add_namespace(x, xp, prefix, ns) < 0)
goto done;
/* Add prefix to x, if any */
if (prefix && xml_prefix_set(x, prefix) < 0)
@ -695,493 +656,6 @@ xml_namespace_change(cxobj *x,
return retval;
}
/*!
*/
static int
xml_default_create1(yang_stmt *y,
cxobj *xt,
cxobj **xcp)
{
int retval = -1;
char *namespace;
char *prefix;
int ret;
cxobj *xc = NULL;
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xc, y);
/* assign right prefix */
if ((namespace = yang_find_mynamespace(y)) != NULL){
prefix = NULL;
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
goto done;
if (ret){ /* Namespace found, prefix returned in prefix */
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
else{ /* Namespace does not exist in target, must add it w xmlns attr.
use source prefix */
if (add_namespace(xc, xc, prefix, namespace) < 0)
goto done;
/* Add prefix to x, if any */
if (prefix && xml_prefix_set(xc, prefix) < 0)
goto done;
}
}
if (xml_addsub(xt, xc) < 0)
goto done;
*xcp = xc;
retval = 0;
done:
return retval;
}
/*! Create leaf from default value
*
* @param[in] y Yang spec
* @param[in] xt XML tree
* @param[in] top Use default namespace (if you create xmlns statement)
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default_create(yang_stmt *y,
cxobj *xt,
int top)
{
int retval = -1;
cxobj *xc = NULL;
cxobj *xb;
char *str;
cg_var *cv;
if (xml_default_create1(y, xt, &xc) < 0)
goto done;
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL){
clicon_err(OE_UNIX, ENOENT, "No yang cv of %s", yang_argument_get(y));
goto done;
}
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
retval = 0;
done:
return retval;
}
/*! Try to see if intermediate nodes are necessary for default values, create if so
*
* @param[in] yt Yang container (no-presence)
* @param[in] state Set if global state, otherwise config
* @param[out] createp Need to create XML container
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_nopresence_try(yang_stmt *yt,
int state,
int *createp)
{
int retval = -1;
yang_stmt *y;
if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
clicon_err(OE_XML, EINVAL, "yt argument is not container");
goto done;
}
*createp = 0;
y = NULL;
while ((y = yn_each(yt, y)) != NULL) {
switch (yang_keyword_get(y)){
case Y_LEAF:
/* Default value exists */
if (!cv_flag(yang_cv_get(y), V_UNSET)){
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(y))
;
else
/* Need to create container */
*createp = 1;
goto ok;
}
break;
case Y_CONTAINER:
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
/* If this is non-presence, (and it does not exist in xt) call recursively
* and create nodes if any default value exist first. Then continue and populate?
*/
if (xml_nopresence_try(y, state, createp) < 0)
goto done;
if (*createp)
goto ok;
}
break;
default:
break;
} /* switch */
}
ok:
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on (children of) one single xml node
*
* Not recursive, except in one case with one or several non-presence containers, in which case
* XML containers may be created to host default values. That code may be a little too recursive.
* @param[in] yt Yang spec
* @param[in] xt XML tree (with yt as spec of xt, informally)
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* XXX If state, should not add config defaults
* if (state && yang_config(yc))
*/
static int
xml_default1(yang_stmt *yt,
cxobj *xt,
int state)
{
int retval = -1;
yang_stmt *yc;
cxobj *xc;
int top = 0; /* Top symbol (set default namespace) */
int create = 0;
char *xpath;
int nr = 0;
int hit = 0;
cg_var *cv;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
goto done;
}
switch (yang_keyword_get(yt)){
case Y_MODULE:
case Y_SUBMODULE:
top++;
case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
case Y_LIST:
case Y_INPUT:
case Y_OUTPUT:
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
/* If config parameter and local is config false */
if (!state && !yang_config(yc))
continue;
switch (yang_keyword_get(yc)){
case Y_LEAF:
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(yc))
break;
if ((cv = yang_cv_get(yc)) == NULL){
clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should",
yang_argument_get(yc));
goto done;
}
if (!cv_flag(cv, V_UNSET)){ /* Default value exists */
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
goto done;
xml_sort(xt);
}
}
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?
*/
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such container exist, recursively try if needed */
if (xml_nopresence_try(yc, state, &create) < 0)
goto done;
if (create){
/* Retval shows there is a default value need to create the
* container */
if (xml_default_create1(yc, xt, &xc) < 0)
goto done;
xml_sort(xt);
/* Then call it recursively */
if (xml_default1(yc, xc, state) < 0)
goto done;
}
}
}
break;
default:
break;
}
}
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on existing leaf children of this node
*
* Assume yang is bound to the tree
* @param[in] xt XML tree
* @param[in] state If set expand defaults also for state data, otherwise only config
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default(cxobj *xt,
int state)
{
int retval = -1;
yang_stmt *ys;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
if (xml_default1(ys, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Recursively fill in default values in an XML tree
* @param[in] xt XML tree
* @param[in] state If set expand defaults also for state data, otherwise only config
* @retval 0 OK
* @retval -1 Error
* @see xml_global_defaults
*/
int
xml_default_recurse(cxobj *xn,
int state)
{
int retval = -1;
cxobj *x;
yang_stmt *y;
if (xml_default(xn, state) < 0)
goto done;
x = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((y = (yang_stmt*)xml_spec(x)) != NULL){
if (!state && !yang_config(y))
continue;
}
if (xml_default_recurse(x, state) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] xt XML tree
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_global_defaults_create(cxobj *xt,
yang_stmt *yspec,
int state)
{
int retval = -1;
yang_stmt *ymod = NULL;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
goto done;
}
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (xml_default1(ymod, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] h Clixon handle
* @param[in] xt XML tree, assume already filtered with xpath
* @param[in] xpath Filter global defaults with this and merge with xt
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* Uses cache?
* @see xml_default_recurse
*/
int
xml_global_defaults(clicon_handle h,
cxobj *xt,
cvec *nsc,
const char *xpath,
yang_stmt *yspec,
int state)
{
int retval = -1;
db_elmnt de0 = {0,};
db_elmnt *de = NULL;
cxobj *xcache = NULL;
cxobj *xpart = NULL;
cxobj **xvec = NULL;
size_t xlen;
int i;
cxobj *x0;
int ret;
char *key;
/* Use different keys for config and state */
key = state ? "global-defaults-state" : "global-defaults-config";
/* First get or compute global xml tree cache */
if ((de = clicon_db_elmnt_get(h, key)) == NULL){
/* Create it */
if ((xcache = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_global_defaults_create(xcache, yspec, state) < 0)
goto done;
de0.de_xml = xcache;
clicon_db_elmnt_set(h, key, &de0);
}
else
xcache = de->de_xml;
/* Here xcache has all global defaults. Now find the matching nodes
* XXX: nsc as 2nd argument
*/
if (xpath_vec(xcache, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* Iterate through match vector
* For every node found in x0, mark the tree up to t1
*/
for (i=0; i<xlen; i++){
x0 = xvec[i];
xml_flag_set(x0, XML_FLAG_MARK);
xml_apply_ancestor(x0, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
/* Create a new tree and copy over the parts from the cache that matches xpath */
if ((xpart = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_copy_marked(xcache, xpart) < 0) /* config */
goto done;
if (xml_apply(xcache, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
if (xml_apply(xpart, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
/* Merge global pruned tree with xt */
if ((ret = xml_merge(xt, xpart, yspec, NULL)) < 1) /* XXX reason */
goto done;
retval = 0;
done:
if (xpart)
xml_free(xpart);
if (xvec)
free(xvec);
return retval;
}
/*! Recursively find empty nopresence containers and default leaves, optionally purge
*
* @param[in] xn XML tree
* @param[in] purge 0: Dont remove any nodes
* 1: Remove config sub-nodes that are empty non-presence container or default leaf
* 2: Remove all sub-nodes that are empty non-presence container or default leaf
* @retval 1 Node is an (recursive) empty non-presence container or default leaf
* @retval 0 Other node
* @retval -1 Error
* @note xn is not itself removed if purge
* @note for purge=1 are removed only if config or no yang spec(!)
*/
int
xml_defaults_nopresence(cxobj *xn,
int purge)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
yang_stmt *yn;
yang_stmt *y;
int rmx = 0; /* If set, remove this xn */
int ret;
enum rfc_6020 keyw;
int config = 1;
if ((yn = xml_spec(xn)) != NULL){
keyw = yang_keyword_get(yn);
if (keyw == Y_CONTAINER &&
yang_find(yn, Y_PRESENCE, NULL) == NULL)
rmx = 1;
else if (keyw == Y_LEAF &&
xml_flag(xn, XML_FLAG_DEFAULT))
rmx = 1;
config = yang_config_ancestor(yn);
}
/* Loop thru children */
x = NULL;
xprev = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((ret = xml_defaults_nopresence(x, purge)) < 0)
goto done;
if (ret == 1){
switch (purge){
case 1: /* config nodes only */
if (!config)
break;
if ((y = xml_spec(x)) != NULL &&
!yang_config(y))
break;
/* fall thru */
case 2: /* purge all nodes */
if (xml_purge(x) < 0)
goto done;
x = xprev;
break;
default:
break;
}
}
else if (rmx)
/* May switch an empty non-presence container (rmx=1) to non-empty non-presence container (rmx=0) */
rmx = 0;
}
retval = rmx;
done:
return retval;
}
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/
@ -1505,7 +979,7 @@ assign_namespace(cxobj *x1, /* target */
}
}
}
if (add_namespace(x1, x1, prefix1, ns) < 0)
if (xml_add_namespace(x1, x1, prefix1, ns) < 0)
goto done;
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
@ -1611,7 +1085,7 @@ assign_namespace_body(cxobj *x0, /* source */
if (namespace1 && strcmp(namespace, namespace1)==0)
continue;
/* No, add entry */
if (add_namespace(x1, x1, prefix1, namespace) < 0)
if (xml_add_namespace(x1, x1, prefix1, namespace) < 0)
goto done;
}
}
@ -2356,107 +1830,3 @@ purge_tagged_nodes(cxobj *xn,
return retval;
}
/*! Add default attribute to node with default value.
*
* Used in with-default code for report-all-tagged
* @param[in] x XML node
* @param[in] flags Flags indicatiing default nodes
* @retval 0 OK
* @retval -1 Error
*/
int
xml_add_default_tag(cxobj *x,
uint16_t flags)
{
int retval = -1;
cxobj *xattr;
if (xml_flag(x, flags)) {
/* set default attribute */
if ((xattr = xml_new("default", x, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xattr, "true") < 0)
goto done;
if (xml_prefix_set(xattr, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Set flag on node having schema default value. (non-config)
*
* Used in with-default code for trim/report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_default_value for config default value
*/
int
xml_flag_state_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if (yang_config_ancestor(y) == 1)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}
/*! Set flag on node having schema default value. (config)
*
* Used in with-default code for trim and report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_state_default_value for non-config default value
*/
int
xml_flag_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}

View file

@ -69,6 +69,7 @@
#include "clixon_xml.h"
#include "clixon_yang_module.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h"
/* Undefine if you want to ensure strict namespace assignment on all netconf
@ -699,3 +700,42 @@ xml2prefix(cxobj *xn,
}
/*! Add prefix:namespace pair to xml node, set cache, etc
* @param[in] x XML node whose namespace should change
* @param[in] xp XML node where namespace attribute should be declared (can be same)
* @param[in] prefix1 Use this prefix
* @param[in] namespace Use this namespace
* @note x and xp must be different if x is an attribute and may be different otherwise
*/
int
xml_add_namespace(cxobj *x,
cxobj *xp,
char *prefix,
char *namespace)
{
int retval = -1;
cxobj *xa = NULL;
/* Add binding to x1p. We add to parent due to heurestics, so we dont
* end up in adding it to large number of siblings
*/
if (nscache_set(x, prefix, namespace) < 0)
goto done;
/* Create xmlns attribute to x1p/x1 XXX same code v */
if (prefix){
if ((xa = xml_new(prefix, xp, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", xp, CX_ATTR)) == NULL)
goto done;
}
if (xml_value_set(xa, namespace) < 0)
goto done;
xml_sort(xp); /* Ensure attr is first / XXX xml_insert? */
retval = 0;
done:
return retval;
}

View file

@ -963,7 +963,7 @@ yn_insert1(yang_stmt *ys_parent,
/*! Iterate through all yang statements from a yang node
*
* @param[in] yparent yang statement whose children should be iterated
* @param[in] yparent yang statement whose children are iterated
* @param[in] yprev previous child, or NULL on init
* @code
* yang_stmt *yprev = NULL;
@ -1431,6 +1431,32 @@ yang_myroot(yang_stmt *ys)
return NULL;
}
/*! Get closest yang case and choice, if any
*/
int
choice_case_get(yang_stmt *yc,
yang_stmt **ycase,
yang_stmt **ychoice)
{
yang_stmt *yp;
if ((yp = yang_parent_get(yc)) == NULL)
return 0;
if (yang_keyword_get(yp) == Y_CASE){
if (ycase)
*ycase = yp;
*ychoice = yang_parent_get(yp);
return 1;
}
else if (yang_keyword_get(yp) == Y_CHOICE){
if (ycase)
*ycase = NULL;
*ychoice = yp;
return 1;
}
return 0;
}
/*! If a given yang stmt has a choice/case as parent, return the choice statement
*/
yang_stmt *

161
test/test_default_choice.sh Executable file
View file

@ -0,0 +1,161 @@
#!/usr/bin/env bash
# Tests of defaults in choices.
# From RFC7950 Sec 7.9.3
# 1. Default case, the default if no child nodes from any of the choice's cases exist
# 2. Default for child nodes under a case are only used if one of the nodes under that case
# is present
# 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/choice.xml
clidir=$dir/cli
fyang=$dir/transfer.yang
test -d ${clidir} || rm -rf ${clidir}
mkdir $clidir
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>clixon-restconf:allow-auth-none</CLICON_FEATURE> <!-- Use auth-type=none -->
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_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>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF
# See example in RFC 7950 Sec 7.9.3
cat <<EOF > $fyang
module transfer{
yang-version 1.1;
namespace "urn:example:transfer";
prefix tr;
grouping transfer-container {
description "Example from RFC 7950 Sec 7.9.3";
container transfer {
choice how {
default interval;
case interval {
leaf interval {
type uint16;
units minutes;
default 30;
}
}
case daily {
leaf daily {
type empty;
}
leaf time-of-day {
type string;
units 24-hour-clock;
default "01.00";
}
}
case manual {
leaf manual {
type empty;
}
}
}
}
}
uses transfer-container;
/* Same but within list */
list li{
key x;
leaf x {
type int32;
}
uses transfer-container;
}
}
EOF
cat <<EOF > $clidir/ex.cli
# Clixon example specification
CLICON_MODE="example";
CLICON_PROMPT="%U@%H %W> ";
CLICON_PLUGIN="example_cli";
# Autocli syntax tree operations
set @datamodel, cli_auto_set();
delete("Delete a configuration item") {
@datamodel, cli_auto_del();
all("Delete whole candidate configuration"), delete_all("candidate");
}
validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit();
discard("Discard edits (rollback 0)"), discard_changes();
show("Show a particular state of the system"){
configuration("Show configuration"), cli_show_auto_mode("candidate", "text", true, false);{
cli("Show configuration as CLI commands"), cli_show_auto_mode("candidate", "cli", true, false, "report-all", "set ");
xml("Show configuration as XML"), cli_show_auto_mode("candidate", "xml", true, false, NULL);
}
}
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
sudo pkill -f clixon_backend # to be sure
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend"
wait_backend
new "Default value expected: interval=30"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source><with-defaults xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\">report-all</with-defaults></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><transfer xmlns=\"urn:example:transfer\"><interval>30</interval></transfer></data></rpc-reply>"
new "Set transfer/daily"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><transfer xmlns=\"urn:example:transfer\"><daily/></transfer></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Default value expected: time-of-day=01:00"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source><with-defaults xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\">report-all</with-defaults></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><transfer xmlns=\"urn:example:transfer\"><daily/><time-of-day>01.00</time-of-day></transfer></data></rpc-reply>"
new "Set list element transfer container"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><li xmlns=\"urn:example:transfer\"><x>42</x></li></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Default value expected: interval=30"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/tr:li\" xmlns:tr=\"urn:example:transfer\"/><with-defaults xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\">report-all</with-defaults></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><li xmlns=\"urn:example:transfer\"><x>42</x><transfer><interval>30</interval></transfer></li></data></rpc-reply>"
new "Set transfer/daily"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><li xmlns=\"urn:example:transfer\"><x>42</x><transfer xmlns=\"urn:example:transfer\"><daily/></transfer></li></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Default value expected: time-of-day=01:00"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/tr:li\" xmlns:tr=\"urn:example:transfer\"/><with-defaults xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\">report-all</with-defaults></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><li xmlns=\"urn:example:transfer\"><x>42</x><transfer><daily/><time-of-day>01.00</time-of-day></transfer></li></data></rpc-reply>"
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
fi
rm -rf $dir
new "endtest"
endtest