* Fixed: [when condition error under augment in restconf #227](https://github.com/clicon/clixon/issues/227)

* As part of this fix added custom constant XML_PARENT_CANDIDATE
This commit is contained in:
Olof hagsand 2021-05-25 15:21:45 +02:00
parent 5b39418e92
commit 1ef7a280d7
8 changed files with 178 additions and 31 deletions

View file

@ -81,6 +81,7 @@ Users may have to change how they access the system
### Corrected Bugs ### Corrected Bugs
* Fixed: [when condition error under augment in restconf #227](https://github.com/clicon/clixon/issues/227)
* Fixed: [Using YANG union with decimal64 and string leads to regexp match fail #226](https://github.com/clicon/clixon/issues/226) * Fixed: [Using YANG union with decimal64 and string leads to regexp match fail #226](https://github.com/clicon/clixon/issues/226)
* Fixed: [xpath function count did not work properly #224](https://github.com/clicon/clixon/issues/224) * Fixed: [xpath function count did not work properly #224](https://github.com/clicon/clixon/issues/224)
* Fixed: RESTCONF Native: Failed binding of socket in network namespace caused process zombie * Fixed: RESTCONF Native: Failed binding of socket in network namespace caused process zombie

View file

@ -83,13 +83,32 @@
*/ */
#define STATE_ORDERED_BY_SYSTEM #define STATE_ORDERED_BY_SYSTEM
/* Top-symbol in clixon datastores /*! Top-symbol in clixon datastores
* This is traditionally same as NETCONF_INPUT_CONFIG ("config") but can be different * This is traditionally same as NETCONF_INPUT_CONFIG ("config") but can be different
* If you change this, you need to change test shell variable in lib.sh: DATASTORE_TOP * If you change this, you need to change test shell variable in lib.sh: DATASTORE_TOP
* Consider making this an option or configure option * Consider making this an option (but this has bootstrap problems) or configure option
*/ */
#define DATASTORE_TOP_SYMBOL "config" #define DATASTORE_TOP_SYMBOL "config"
/* Name of default netns for clixon-restconf.yang socket/namespace field /*! Name of default netns for clixon-restconf.yang socket/namespace field
* Restconf allows opening sockets in different network namespaces. This is teh name of
* "host"/"default" namespace. Unsure what to really label this but seems like there is differing
* consensus on how to label it.
* Either find that proper label, or move it to a option
*/ */
#define RESTCONF_NETNS_DEFAULT "default" #define RESTCONF_NETNS_DEFAULT "default"
/*! Set a temporary parent for use in special case "when" xpath calls
* Problem is when changing an existing (candidate) in-memory datastore that yang "when" conditionals
* should be changed in clixon_datastore_write.c:text_modify().
* Problem is that the tree is in an intermediate state so that a when condition may not see the
* full context.
* More specifically, new nodes (x0) are created without hooking them into the existing parent (x0p)
* and thus an xpath on the form ".."/PARENT may not be evaluated as they should. x0 is eventually
* added to its parent but then it is more difficult to check trhe when condition.
* This fix add the parent x0p as a "candidate" so that the xpath-eval function can use it as
* an alernative if it exists.
* Note although this solves many usecases involving parents and absolute paths, itstill does not
* solve all usecases, such as absolute usecases where the added node is looked for
*/
#define XML_PARENT_CANDIDATE

View file

@ -186,6 +186,10 @@ int nscache_replace(cxobj *x, cvec *ns);
int xmlns_set(cxobj *x, char *prefix, char *ns); int xmlns_set(cxobj *x, char *prefix, char *ns);
cxobj *xml_parent(cxobj *xn); cxobj *xml_parent(cxobj *xn);
int xml_parent_set(cxobj *xn, cxobj *parent); int xml_parent_set(cxobj *xn, cxobj *parent);
#ifdef XML_PARENT_CANDIDATE
cxobj *xml_parent_candidate(cxobj *xn);
int xml_parent_candidate_set(cxobj *xn, cxobj *parent);
#endif /* XML_PARENT_CANDIDATE */
uint16_t xml_flag(cxobj *xn, uint16_t flag); uint16_t xml_flag(cxobj *xn, uint16_t flag);
int xml_flag_set(cxobj *xn, uint16_t flag); int xml_flag_set(cxobj *xn, uint16_t flag);

View file

@ -205,7 +205,7 @@ check_body_namespace(cxobj *x0,
/*! Check yang when condition between a new xml x1 and old x0 /*! Check yang when condition between a new xml x1 and old x0
* *
* check if there is a when condition. First try it on the new request (x1), then on the * Check if there is a when condition. First try it on the new request (x1), then on the
* existing (x0). * existing (x0).
* @param[in] x0p Parent of x0 * @param[in] x0p Parent of x0
* @param[in] x1 XML tree which modifies base * @param[in] x1 XML tree which modifies base
@ -226,9 +226,9 @@ check_when_condition(cxobj *x0p,
char *xpath = NULL; char *xpath = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
int nr; int nr;
cxobj *x1p;
yang_stmt *y = NULL; yang_stmt *y = NULL;
cbuf *cberr = NULL; cbuf *cberr = NULL;
cxobj *x1p;
if ((y = y0) != NULL || if ((y = y0) != NULL ||
(y = (yang_stmt*)xml_spec(x1)) != NULL){ (y = (yang_stmt*)xml_spec(x1)) != NULL){
@ -272,7 +272,7 @@ check_when_condition(cxobj *x0p,
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x0p Parent of x0 * @param[in] x0p Parent of x0
* @param[in] x0t * @param[in] x0t Top level of existing tree, eg needed for NACM rules
* @param[in] x1 XML tree which modifies base * @param[in] x1 XML tree which modifies base
* @param[in] x1t Request root node (nacm needs this) * @param[in] x1t Request root node (nacm needs this)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
@ -662,7 +662,9 @@ text_modify(clicon_handle h,
if ((x0 = xml_new(x1name, NULL, CX_ELMNT)) == NULL) if ((x0 = xml_new(x1name, NULL, CX_ELMNT)) == NULL)
goto done; goto done;
xml_spec_set(x0, y0); xml_spec_set(x0, y0);
#ifdef XML_PARENT_CANDIDATE
xml_parent_candidate_set(x0, x0p);
#endif
changed++; changed++;
/* Get namespace from x1 /* Get namespace from x1
* Check if namespace exists in x0 parent * Check if namespace exists in x0 parent
@ -730,8 +732,8 @@ text_modify(clicon_handle h,
x1c = NULL; x1c = NULL;
i = 0; i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
x0c = x0vec[i++]; x0c = x0vec[i++];
x1cname = xml_name(x1c);
yc = yang_find_datanode(y0, x1cname); yc = yang_find_datanode(y0, x1cname);
if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t, if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t,
yc, op, yc, op,
@ -742,6 +744,9 @@ text_modify(clicon_handle h,
goto fail; goto fail;
} }
if (changed){ if (changed){
#ifdef XML_PARENT_CANDIDATE
xml_parent_candidate_set(x0, NULL);
#endif
if (xml_insert(x0p, x0, insert, keystr, nscx1) < 0) if (xml_insert(x0p, x0, insert, keystr, nscx1) < 0)
goto done; goto done;
} }
@ -786,7 +791,7 @@ text_modify(clicon_handle h,
/*! Modify a top-level base tree x0 with modification tree x1 /*! Modify a top-level base tree x0 with modification tree x1
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x0t * @param[in] x0t Top level of existing tree, eg needed for NACM rules
* @param[in] x1 XML tree which modifies base * @param[in] x1 XML tree which modifies base
* @param[in] x1t Request root node (nacm needs this) * @param[in] x1t Request root node (nacm needs this)
* @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] yspec Top-level yang spec (if y is NULL)

View file

@ -166,6 +166,9 @@ struct xml{
char *x_prefix; /* namespace localname N, called prefix */ char *x_prefix; /* namespace localname N, called prefix */
uint16_t x_flags; /* Flags according to XML_FLAG_* */ uint16_t x_flags; /* Flags according to XML_FLAG_* */
struct xml *x_up; /* parent node in hierarchy if any */ struct xml *x_up; /* parent node in hierarchy if any */
#ifdef XML_PARENT_CANDIDATE
struct xml *x_up_candidate; /* Candidate parent node for special cases (when+xpath) */
#endif
int _x_vector_i; /* internal use: xml_child_each */ int _x_vector_i; /* internal use: xml_child_each */
int _x_i; /* internal use for sorting: int _x_i; /* internal use for sorting:
see xml_enumerate and xml_cmp */ see xml_enumerate and xml_cmp */
@ -196,6 +199,9 @@ struct xmlbody{
char *xb_prefix; /* namespace localname N, called prefix */ char *xb_prefix; /* namespace localname N, called prefix */
uint16_t xb_flags; /* Flags according to XML_FLAG_* */ uint16_t xb_flags; /* Flags according to XML_FLAG_* */
struct xml *xb_up; /* parent node in hierarchy if any */ struct xml *xb_up; /* parent node in hierarchy if any */
#ifdef XML_PARENT_CANDIDATE
struct xml *xb_up_candidate; /* Candidate parent node for special cases (when+xpath) */
#endif
int _xb_vector_i; /* internal use: xml_child_each */ int _xb_vector_i; /* internal use: xml_child_each */
int _xb_i; /* internal use for sorting: int _xb_i; /* internal use for sorting:
see xml_enumerate and xml_cmp */ see xml_enumerate and xml_cmp */
@ -251,6 +257,7 @@ xml_stats_one(cxobj *x,
{ {
size_t sz = 0; size_t sz = 0;
if (x->x_name) if (x->x_name)
sz += strlen(x->x_name) + 1; sz += strlen(x->x_name) + 1;
if (x->x_prefix) if (x->x_prefix)
@ -587,6 +594,34 @@ xml_parent_set(cxobj *xn,
return 0; return 0;
} }
#ifdef XML_PARENT_CANDIDATE
/*! Get candidate parent of xnode
* @param[in] xn xml node
* @retval parent xml node
*/
cxobj*
xml_parent_candidate(cxobj *xn)
{
if (xn == NULL) {
return NULL;
}
return xn->x_up_candidate;
}
/*! Set candidate parent of xml node
* @param[in] xn xml node
* @param[in] parent pointer to candidate parent xml node
* @retval 0 OK
*/
int
xml_parent_candidate_set(cxobj *xn,
cxobj *parent)
{
xn->x_up_candidate = parent;
return 0;
}
#endif /* XML_PARENT_CANDIDATE */
/*! Get xml node flags, used for internal algorithms /*! Get xml node flags, used for internal algorithms
* @param[in] xn xml node * @param[in] xn xml node
* @retval flag Flag value(s), see XML_FLAG_MARK et al * @retval flag Flag value(s), see XML_FLAG_MARK et al
@ -1420,8 +1455,7 @@ xml_child_rm(cxobj *xp,
* @param[in] xc xml child node to be removed * @param[in] xc xml child node to be removed
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @note you should not remove xchild in loop (unless yoy keep track of xprev) * @note you should not remove xchild in loop (unless you keep track of xprev)
*
* @see xml_child_rm Remove a child of a node * @see xml_child_rm Remove a child of a node
*/ */
int int

View file

@ -392,7 +392,12 @@ xp_eval_step(xp_ctx *xc0,
xc->xc_nodeset = NULL; xc->xc_nodeset = NULL;
for (i=0; i<veclen; i++){ for (i=0; i<veclen; i++){
x = vec[i]; x = vec[i];
if ((xp = xml_parent(x)) != NULL) if ((xp = xml_parent(x)) != NULL
#ifdef XML_PARENT_CANDIDATE
/* Also check "candidate" parent for special when use-case */
|| (xp = xml_parent_candidate(x)) != NULL
#endif /* XML_PARENT_CANDIDATE */
)
if (cxvec_append(xp, &xc->xc_nodeset, &xc->xc_size) < 0) if (cxvec_append(xp, &xc->xc_nodeset, &xc->xc_size) < 0)
goto done; goto done;
} }
@ -979,8 +984,13 @@ xp_eval(xp_ctx *xc,
case XP_ABSPATH: case XP_ABSPATH:
/* Set context node to top node, and nodeset to that node only */ /* Set context node to top node, and nodeset to that node only */
x = xc->xc_node; x = xc->xc_node;
#ifdef XML_PARENT_CANDIDATE
while (xml_parent(x) != NULL || xml_parent_candidate(x) != NULL)
x = xml_parent(x)?xml_parent(x):xml_parent_candidate(x);
#else
while (xml_parent(x) != NULL) while (xml_parent(x) != NULL)
x = xml_parent(x); x = xml_parent(x);
#endif
xc->xc_node = x; xc->xc_node = x;
xc->xc_nodeset[0] = x; xc->xc_nodeset[0] = x;
xc->xc_size=1; xc->xc_size=1;

View file

@ -4,7 +4,6 @@
# 1. set/get/validate matching and non-matching when statement # 1. set/get/validate matching and non-matching when statement
# 2 RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list, the # 2 RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list, the
# "uses" statement MUST NOT have a "when" statement. # "uses" statement MUST NOT have a "when" statement.
# XXX:Originally for cli tests but not specific to cli (clean when done?)
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -69,7 +68,7 @@ commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit(); quit("Quit"), cli_quit();
EOF EOF
# Start backend, add entry with matching name / non-matrching and check get/validation
function testrun() function testrun()
{ {
new "test params: -f $cfg" new "test params: -f $cfg"
@ -81,8 +80,6 @@ function testrun()
fi fi
new "start backend -s init -f $cfg" new "start backend -s init -f $cfg"
start_backend -s init -f $cfg start_backend -s init -f $cfg
fi fi
new "wait backend" new "wait backend"
wait_backend wait_backend
@ -129,6 +126,7 @@ function testrun()
new "netconf validate default set" new "netconf validate default set"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"
# Check if premature kill # Check if premature kill
pid=$(pgrep -u root -f clixon_backend) pid=$(pgrep -u root -f clixon_backend)
@ -137,6 +135,7 @@ function testrun()
fi fi
# kill backend # kill backend
stop_backend -f $cfg stop_backend -f $cfg
fi
} }
cat <<EOF > $fyang cat <<EOF > $fyang
@ -198,9 +197,6 @@ EOF
new "2. augment/uses/when test edit,default (same but with augment)" new "2. augment/uses/when test edit,default (same but with augment)"
testrun testrun
new "3. RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list"
# the "uses" statement MUST NOT have a "when" statement.
cat <<EOF > $fyang cat <<EOF > $fyang
module example { module example {
namespace "urn:example:clixon"; namespace "urn:example:clixon";
@ -221,14 +217,91 @@ module example {
} }
} }
EOF EOF
new "3. RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list"
# the "uses" statement MUST NOT have a "when" statement.
if [ ${valgrindtest} -eq 0 ]; then # Error dont cleanup mem OK if [ ${valgrindtest} -eq 0 ]; then # Error dont cleanup mem OK
new "start backend -s init -f $cfg, expect yang when fail" new "start backend -s init -f $cfg, expect yang when fail"
expectpart "$(sudo $clixon_backend -F -D $DBG -s init -f $cfg 2>&1)" 255 "Yang error: Key leaf 'name' defined in grouping 'value-top' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5" expectpart "$(sudo $clixon_backend -F -D $DBG -s init -f $cfg 2>&1)" 255 "Yang error: Key leaf 'name' defined in grouping 'value-top' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5"
fi fi
cat <<EOF > $fyang
module example {
namespace "urn:example:clixon";
prefix ex;
leaf allow {
type boolean;
default false;
}
grouping value-top {
leaf value{
type string;
default foo;
}
}
container table{
list parameter{
key name;
leaf name{
type string;
}
uses value-top {
when "../../ex:allow = 'true'";
}
}
}
}
EOF
new "3. Check a top-level value as allowed value"
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend"
wait_backend
new "netconf set allow true"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><allow xmlns=\"urn:example:clixon\">true</allow></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf set value expect OK"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name><value>42</value></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf set allow false"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><allow xmlns=\"urn:example:clixon\">false</allow></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf set value expect fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name><value>42</value></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>value</bad-element></error-info><error-severity>error</error-severity><error-message>Node 'value' tagged with 'when' condition '../../ex:allow = 'true'' in module 'example' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)</error-message></rpc-error></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 # kill backend
stop_backend -f $cfg stop_backend -f $cfg
fi
rm -rf $dir rm -rf $dir

View file

@ -484,7 +484,7 @@ module clixon-config {
type string; type string;
default "/usr/local/sbin"; default "/usr/local/sbin";
description description
"Path to clixon-restconf daemon binary as used by backend if started internally "Path to dir of clixon-restconf daemon binary as used by backend if started internally
Discussion: Somewhat problematic to have it as run time option. It may think it Discussion: Somewhat problematic to have it as run time option. It may think it
should be known at configure or install time, but for example the main docker should be known at configure or install time, but for example the main docker
installation moves the binaries, and this may be true elsewehere too. installation moves the binaries, and this may be true elsewehere too.
@ -520,7 +520,8 @@ module clixon-config {
leaf CLICON_RESTCONF_USER { leaf CLICON_RESTCONF_USER {
type string; type string;
description description
"User name for restconf. "Run clixon_daemon as this user
When drop privileges is used, the daemon will drop privileges to this user.
In pre-5.2 code this was configured as compile-time constant WWWUSER with In pre-5.2 code this was configured as compile-time constant WWWUSER with
default value www-data default value www-data
See also CLICON_PRIVILEGES setting"; See also CLICON_PRIVILEGES setting";