Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc.
This commit is contained in:
parent
7a8f242a09
commit
058a14579f
16 changed files with 412 additions and 86 deletions
|
|
@ -79,6 +79,7 @@
|
|||
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
|
||||
|
||||
### API changes on existing features (you may need to change your code)
|
||||
* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced.
|
||||
* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete.
|
||||
* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file.
|
||||
* For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily.
|
||||
|
|
@ -94,6 +95,7 @@
|
|||
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
|
||||
|
||||
### Minor changes
|
||||
* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc.
|
||||
* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC.
|
||||
* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK.
|
||||
* This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath`
|
||||
|
|
|
|||
|
|
@ -768,6 +768,7 @@ yang2cli_stmt(clicon_handle h,
|
|||
if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_CASE:
|
||||
case Y_SUBMODULE:
|
||||
case Y_MODULE:
|
||||
for (i=0; i<ys->ys_len; i++)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
* @param[in] cb Packet buffer
|
||||
*/
|
||||
static int
|
||||
process_incoming_packet(clicon_handle h,
|
||||
netconf_input_packet(clicon_handle h,
|
||||
cbuf *cb)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -94,6 +94,8 @@ process_incoming_packet(clicon_handle h,
|
|||
cxobj *xc;
|
||||
yang_spec *yspec;
|
||||
int ret;
|
||||
cxobj *xa;
|
||||
cxobj *xa2;
|
||||
|
||||
clicon_debug(1, "RECV");
|
||||
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
|
||||
|
|
@ -110,7 +112,7 @@ process_incoming_packet(clicon_handle h,
|
|||
/* Parse incoming XML message */
|
||||
if (xml_parse_string(str, yspec, &xreq) < 0){
|
||||
free(str0);
|
||||
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
|
||||
if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0)
|
||||
goto done;
|
||||
netconf_output_encap(1, cbret, "rpc-error");
|
||||
goto done;
|
||||
|
|
@ -143,8 +145,13 @@ process_incoming_packet(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
else{ /* there is a return message in xret */
|
||||
cxobj *xa, *xa2;
|
||||
assert(xret);
|
||||
|
||||
if (xret == NULL){
|
||||
if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0)
|
||||
goto done;
|
||||
netconf_output_encap(1, cbret, "rpc-error");
|
||||
goto done;
|
||||
}
|
||||
if ((xc = xml_child_i(xret,0))!=NULL){
|
||||
xa=NULL;
|
||||
/* Copy message-id attribute from incoming to reply.
|
||||
|
|
@ -228,7 +235,7 @@ netconf_input_cb(int s,
|
|||
/* OK, we have an xml string from a client */
|
||||
/* Remove trailer */
|
||||
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
|
||||
if (process_incoming_packet(h, cb) < 0)
|
||||
if (netconf_input_packet(h, cb) < 0)
|
||||
; //goto done; // ignore errors
|
||||
if (cc_closed)
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -314,7 +314,6 @@ netconf_edit_config(clicon_handle h,
|
|||
enum operation_type operation = OP_MERGE;
|
||||
enum test_option testopt = TEST_THEN_SET;/* only supports this */
|
||||
enum error_option erropt = STOP_ON_ERROR; /* only supports this */
|
||||
cxobj *xc; /* config */
|
||||
cxobj *x;
|
||||
cxobj *xfilter;
|
||||
char *ftype = NULL;
|
||||
|
|
@ -366,18 +365,8 @@ netconf_edit_config(clicon_handle h,
|
|||
"</rpc-error></rpc-reply>");
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */
|
||||
if ((xc = xpath_first(xn, "config")) != NULL){
|
||||
#if 0
|
||||
/* application-specific code registers 'config' */
|
||||
if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){
|
||||
goto ok;
|
||||
}
|
||||
#endif
|
||||
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
|
|||
|
|
@ -715,8 +715,9 @@ text_modify(struct text_handle *th,
|
|||
if (op==OP_NONE)
|
||||
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
||||
}
|
||||
/* First pass: mark existing children in base */
|
||||
/* Loop through children of the modification tree */
|
||||
/* First pass: Loop through children of the x1 modification tree
|
||||
* collect matching nodes from x0 in x0vec (no changes to x0 children)
|
||||
*/
|
||||
if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "calloc");
|
||||
goto done;
|
||||
|
|
@ -732,17 +733,29 @@ text_modify(struct text_handle *th,
|
|||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
x0c = NULL;
|
||||
if (match_base_child(x0, x1c, &x0c, yc) < 0)
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
x0vec[i++] = x0c;
|
||||
#if 1
|
||||
if (x0c && (yc != xml_spec(x0c))){
|
||||
/* There is a match but is should be replaced (choice)*/
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
x0c = NULL;
|
||||
}
|
||||
#endif
|
||||
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
|
||||
}
|
||||
/* Second pass: modify tree */
|
||||
/* Second pass: Loop through children of the x1 modification tree again
|
||||
* Now potentially modify x0:s children
|
||||
* Here x0vec contains one-to-one matching nodes of x1:s children.
|
||||
*/
|
||||
x1c = NULL;
|
||||
i = 0;
|
||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||
x1cname = xml_name(x1c);
|
||||
x0c = x0vec[i++];
|
||||
yc = yang_find_datanode(y0, x1cname);
|
||||
if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0)
|
||||
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
|
||||
goto done;
|
||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||
if (ret == 0)
|
||||
|
|
@ -862,8 +875,16 @@ text_modify_top(struct text_handle *th,
|
|||
goto fail;
|
||||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
if (match_base_child(x0, x1c, &x0c, yc) < 0)
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
#if 1
|
||||
if (x0c && (yc != xml_spec(x0c))){
|
||||
/* There is a match but is should be replaced (choice)*/
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
x0c = NULL;
|
||||
}
|
||||
#endif
|
||||
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
|
||||
goto done;
|
||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||
|
|
|
|||
|
|
@ -48,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword,
|
|||
int upper);
|
||||
cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
|
||||
int xml_sort_verify(cxobj *x, void *arg);
|
||||
int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc);
|
||||
int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp);
|
||||
|
||||
#endif /* _CLIXON_XML_SORT_H */
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
|
|||
yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
|
||||
char *yang_find_myprefix(yang_stmt *ys);
|
||||
char *yang_find_mynamespace(yang_stmt *ys);
|
||||
yang_node *yang_choice(yang_stmt *y);
|
||||
int yang_order(yang_stmt *y);
|
||||
int yang_print(FILE *f, yang_node *yn);
|
||||
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ object.
|
|||
|
||||
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;}
|
||||
|
||||
/* add _yy to error paramaters */
|
||||
/* add _yy to error parameters */
|
||||
#define YY_(msgid) msgid
|
||||
|
||||
#include "clixon_config.h"
|
||||
|
|
|
|||
|
|
@ -189,13 +189,14 @@ xml2cli(FILE *f,
|
|||
if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){
|
||||
if (prepend0)
|
||||
fprintf(f, "%s", prepend0);
|
||||
body = xml_body(x);
|
||||
if (gt == GT_ALL || gt == GT_VARS)
|
||||
fprintf(f, "%s ", xml_name(x));
|
||||
if (index(body, ' '))
|
||||
fprintf(f, "\"%s\"", body);
|
||||
else
|
||||
fprintf(f, "%s", body);
|
||||
if ((body = xml_body(x)) != NULL){
|
||||
if (index(body, ' '))
|
||||
fprintf(f, "\"%s\"", body);
|
||||
else
|
||||
fprintf(f, "%s", body);
|
||||
}
|
||||
fprintf(f, "\n");
|
||||
goto ok;
|
||||
}
|
||||
|
|
@ -508,32 +509,56 @@ xml_yang_validate_add(cxobj *xt,
|
|||
int retval = -1;
|
||||
cg_var *cv = NULL;
|
||||
char *reason = NULL;
|
||||
yang_stmt *yc;
|
||||
yang_stmt *yt; /* yang spec of xt going in */
|
||||
yang_stmt *yc; /* yang spec of yt child */
|
||||
yang_stmt *yx; /* yang spec of xt children */
|
||||
yang_node *yp; /* yang spec of parent of yang spec of xt children */
|
||||
int i;
|
||||
yang_stmt *ys;
|
||||
char *body;
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
/* if not given by argument (overide) use default link
|
||||
and !Node has a config sub-statement and it is false */
|
||||
if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){
|
||||
switch (ys->ys_keyword){
|
||||
if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
|
||||
switch (yt->ys_keyword){
|
||||
case Y_RPC:
|
||||
case Y_INPUT:
|
||||
case Y_LIST:
|
||||
/* fall thru */
|
||||
case Y_CONTAINER:
|
||||
for (i=0; i<ys->ys_len; i++){
|
||||
yc = ys->ys_stmt[i];
|
||||
if (yc->ys_keyword != Y_LEAF)
|
||||
continue;
|
||||
if (yang_config(yc)==0)
|
||||
continue;
|
||||
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
|
||||
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
for (i=0; i<yt->ys_len; i++){
|
||||
yc = yt->ys_stmt[i];
|
||||
switch (yc->ys_keyword){
|
||||
case Y_LEAF:
|
||||
if (yang_config(yc)==0)
|
||||
break;
|
||||
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
|
||||
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case Y_CHOICE:
|
||||
if (yang_mandatory(yc)){
|
||||
/* If there is one child with this choice as parent */
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((yx = xml_spec(x)) != NULL &&
|
||||
(yp = yang_choice(yx)) != NULL &&
|
||||
yp == (yang_node*)yc){
|
||||
break; /* leave loop with x set */
|
||||
}
|
||||
}
|
||||
if (x == NULL){ /* No hit */
|
||||
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -541,7 +566,7 @@ xml_yang_validate_add(cxobj *xt,
|
|||
/* fall thru */
|
||||
case Y_LEAF_LIST:
|
||||
/* validate value against ranges, etc */
|
||||
if ((cv = cv_dup(ys->ys_cv)) == NULL){
|
||||
if ((cv = cv_dup(yt->ys_cv)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv_dup");
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -550,12 +575,12 @@ xml_yang_validate_add(cxobj *xt,
|
|||
*/
|
||||
if ((body = xml_body(xt)) != NULL){
|
||||
if (cv_parse1(body, cv, &reason) != 1){
|
||||
if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0)
|
||||
if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if ((ys_cv_validate(cv, ys, &reason)) != 1){
|
||||
if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0)
|
||||
if ((ys_cv_validate(cv, yt, &reason)) != 1){
|
||||
if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
|
|
@ -948,12 +973,12 @@ cvec2xml_1(cvec *cvv,
|
|||
}
|
||||
|
||||
/*! Recursive help function to compute differences between two xml trees
|
||||
* @param[in] x1 First XML tree
|
||||
* @param[in] x2 Second XML tree
|
||||
* @param[in] x1 First XML tree
|
||||
* @param[in] x2 Second XML tree
|
||||
* @param[out] x1vec Pointervector to XML nodes existing in only first tree
|
||||
* @param[out] x1veclen Length of first vector
|
||||
* @param[out] x2vec Pointervector to XML nodes existing in only second tree
|
||||
* @param[out] x2veclen Length of x2vec vector
|
||||
* @param[out] x2vec Pointervector to XML nodes existing in only second tree
|
||||
* @param[out] x2veclen Length of x2vec vector
|
||||
* @param[out] changed_x1 Pointervector to XML nodes changed orig value
|
||||
* @param[out] changed_x2 Pointervector to XML nodes changed wanted value
|
||||
* @param[out] changedlen Length of changed vector
|
||||
|
|
@ -988,13 +1013,21 @@ xml_diff1(yang_stmt *ys,
|
|||
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c));
|
||||
goto done;
|
||||
}
|
||||
if (match_base_child(x2, x1c, &x2c, yc) < 0)
|
||||
if (match_base_child(x2, x1c, yc, &x2c) < 0)
|
||||
goto done;
|
||||
if (x2c == NULL){
|
||||
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
else if (yang_choice(yc)){
|
||||
/* if x1c and x2c are choice/case, then they are changed */
|
||||
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
|
||||
goto done;
|
||||
(*changedlen)--; /* append two vectors */
|
||||
if (cxvec_append(x2c, changed_x2, changedlen) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* if x1c and x2c are leafs w bodies, then they are changed */
|
||||
if (yc->ys_keyword == Y_LEAF){
|
||||
if ((b1 = xml_body(x1c)) == NULL) /* empty type */
|
||||
break;
|
||||
|
|
@ -1024,7 +1057,7 @@ xml_diff1(yang_stmt *ys,
|
|||
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c));
|
||||
goto done;
|
||||
}
|
||||
if (match_base_child(x1, x2c, &x1c, yc) < 0)
|
||||
if (match_base_child(x1, x2c, yc, &x1c) < 0)
|
||||
goto done;
|
||||
if (x1c == NULL)
|
||||
if (cxvec_append(x2c, x2vec, x2veclen) < 0)
|
||||
|
|
@ -1118,7 +1151,8 @@ yang2api_path_fmt_1(yang_stmt *ys,
|
|||
yp->yn_keyword != Y_SUBMODULE){
|
||||
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
|
||||
goto done;
|
||||
cprintf(cb, "/");
|
||||
if (yp->yn_keyword != Y_CHOICE && yp->yn_keyword != Y_CASE)
|
||||
cprintf(cb, "/");
|
||||
}
|
||||
else /* top symbol - mark with name prefix */
|
||||
cprintf(cb, "/%s:", yp->yn_argument);
|
||||
|
|
@ -1128,14 +1162,12 @@ yang2api_path_fmt_1(yang_stmt *ys,
|
|||
cprintf(cb, "%s", ys->ys_argument);
|
||||
}
|
||||
else{
|
||||
#if 1
|
||||
if (ys->ys_keyword == Y_LEAF && yp &&
|
||||
yp->yn_keyword == Y_LIST){
|
||||
if (yang_key_match(yp, ys->ys_argument) == 0)
|
||||
cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
|
||||
cprintf(cb, "%s", ys->ys_argument);
|
||||
}
|
||||
|
|
@ -2202,7 +2234,7 @@ xml_merge1(cxobj *x0,
|
|||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
x0c = NULL;
|
||||
if (yc && match_base_child(x0, x1c, &x0c, yc) < 0)
|
||||
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
|
||||
goto done;
|
||||
|
|
@ -2284,8 +2316,12 @@ xml_merge(cxobj *x0,
|
|||
break;
|
||||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
if (match_base_child(x0, x1c, &x0c, yc) < 0)
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
/* There is a case where x0c and x1c are choice nodes, if so,
|
||||
* it is treated as a match, and x0c will remain
|
||||
* If it is overwritten, then x0c should be removed here.
|
||||
*/
|
||||
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
|
||||
goto done;
|
||||
if (*reason != NULL)
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@
|
|||
* @param[in] xp XML parent, can be NULL.
|
||||
* @param[in] yspec Yang specification (top level)
|
||||
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
|
||||
* @note works for import prefix, but not work for generic XML parsing where
|
||||
* xmlns and xmlns:ns are used.
|
||||
|
|
@ -555,22 +557,18 @@ xml_sort_verify(cxobj *x0,
|
|||
}
|
||||
|
||||
/*! Given child tree x1c, find matching child in base tree x0 and return as x0cp
|
||||
* param[in] x0 Base tree node
|
||||
* param[in] x1c Modification tree child
|
||||
* param[in] yc Yang spec of tree child
|
||||
* param[out] x0cp Matching base tree child (if any)
|
||||
* @note XXX: room for optimization? on 1K calls we have 1M body calls and
|
||||
500K xml_child_each/cvec_each calls.
|
||||
The outer loop is large for large lists
|
||||
The inner loop is small
|
||||
Major time in xml_find_body()
|
||||
Can one do a binary search in the x0 list?
|
||||
*/
|
||||
* @param[in] x0 Base tree node
|
||||
* @param[in] x1c Modification tree child
|
||||
* @param[in] yc Yang spec of tree child
|
||||
* @param[out] x0cp Matching base tree child (if any)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
match_base_child(cxobj *x0,
|
||||
cxobj *x1c,
|
||||
cxobj **x0cp,
|
||||
yang_stmt *yc)
|
||||
match_base_child(cxobj *x0,
|
||||
cxobj *x1c,
|
||||
yang_stmt *yc,
|
||||
cxobj **x0cp)
|
||||
{
|
||||
int retval = -1;
|
||||
cvec *cvk = NULL; /* vector of index keys */
|
||||
|
|
@ -582,8 +580,29 @@ match_base_child(cxobj *x0,
|
|||
char **keyvec = NULL;
|
||||
int i;
|
||||
int yorder;
|
||||
cxobj *x0c = NULL;
|
||||
yang_stmt *y0c;
|
||||
yang_node *y0p;
|
||||
yang_node *yp; /* yang parent */
|
||||
|
||||
*x0cp = NULL; /* return value */
|
||||
*x0cp = NULL; /* init return value */
|
||||
#if 1
|
||||
/* Special case is if yc parent (yp) is choice/case
|
||||
* then find x0 child with same yc even though it does not match lexically
|
||||
* However this will give another y0c != yc
|
||||
*/
|
||||
if ((yp = yang_choice(yc)) != NULL){
|
||||
x0c = NULL;
|
||||
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
|
||||
if ((y0c = xml_spec(x0c)) != NULL &&
|
||||
(y0p = yang_choice(y0c)) != NULL &&
|
||||
y0p == yp)
|
||||
break; /* x0c will have a value */
|
||||
}
|
||||
*x0cp = x0c;
|
||||
goto ok; /* What to do if not found? */
|
||||
}
|
||||
#endif
|
||||
switch (yc->ys_keyword){
|
||||
case Y_CONTAINER: /* Equal regardless */
|
||||
case Y_LEAF: /* Equal regardless */
|
||||
|
|
@ -629,11 +648,12 @@ match_base_child(cxobj *x0,
|
|||
/* Get match. Sorting mode(optimized) or not?*/
|
||||
if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){
|
||||
yorder = yang_order(yc);
|
||||
*x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
|
||||
x0c = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
|
||||
}
|
||||
else{
|
||||
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
|
||||
x0c = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
|
||||
}
|
||||
*x0cp = x0c;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@
|
|||
|
||||
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;}
|
||||
|
||||
/* add _yy to error paramaters */
|
||||
/* add _yy to error parameters */
|
||||
#define YY_(msgid) msgid
|
||||
|
||||
#include "clixon_config.h"
|
||||
|
|
|
|||
|
|
@ -624,6 +624,28 @@ yang_find_mynamespace(yang_stmt *ys)
|
|||
return namespace;
|
||||
}
|
||||
|
||||
/*! If a given yang stmt has a choice/case as parent, return the choice statement
|
||||
*/
|
||||
yang_node *
|
||||
yang_choice(yang_stmt *y)
|
||||
{
|
||||
yang_node *yp;
|
||||
|
||||
if ((yp = y->ys_parent) != NULL){
|
||||
switch (yp->yn_keyword){
|
||||
case Y_CHOICE:
|
||||
return yp;
|
||||
break;
|
||||
case Y_CASE:
|
||||
return yp->yn_parent;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
|
||||
* @retval 0 not found
|
||||
* @retval 1 found
|
||||
|
|
@ -2685,7 +2707,7 @@ yang_mandatory(yang_stmt *ys)
|
|||
{
|
||||
yang_stmt *ym;
|
||||
|
||||
if (ys->ys_keyword != Y_LEAF)
|
||||
if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE)
|
||||
return 0;
|
||||
if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){
|
||||
if (ym->ys_cv == NULL) /* shouldnt happen */
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@
|
|||
|
||||
#define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_yang_parsetext, _YY->yy_linenum); YYERROR;}
|
||||
|
||||
/* add _yy to error paramaters */
|
||||
/* add _yy to error parameters */
|
||||
#define YY_(msgid) msgid
|
||||
|
||||
#include "clixon_config.h"
|
||||
|
|
|
|||
212
test/test_choice.sh
Executable file
212
test/test_choice.sh
Executable file
|
|
@ -0,0 +1,212 @@
|
|||
#!/bin/bash
|
||||
# Choice type and mandatory
|
||||
# Example from RFC7950 Sec 7.9.6
|
||||
# Also test mandatory behaviour as in 7.6.5
|
||||
# (XXX Would need default test in 7.6.4)
|
||||
# Use-case: The ietf-netconf edit-config has a shorthand version of choice w mandatory:
|
||||
# container { choice target { mandatory; leaf candidate; leaf running; }}
|
||||
|
||||
APPNAME=example
|
||||
# include err() and new() functions and creates $dir
|
||||
. ./lib.sh
|
||||
|
||||
cfg=$dir/choice.xml
|
||||
fyang=$dir/type.yang
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<config>
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>system</CLICON_YANG_MODULE_MAIN>
|
||||
<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_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
||||
</config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module system{
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:config";
|
||||
prefix ex;
|
||||
container system{
|
||||
/* From RFC 7950 7.9.6 */
|
||||
container protocol {
|
||||
presence true;
|
||||
choice name {
|
||||
case a {
|
||||
leaf udp {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
case b {
|
||||
leaf tcp {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Same but shorthand */
|
||||
container shorthand {
|
||||
presence true;
|
||||
choice name {
|
||||
leaf udp {
|
||||
type empty;
|
||||
}
|
||||
leaf tcp {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Same with mandatory true */
|
||||
container mandatory {
|
||||
presence true;
|
||||
choice name {
|
||||
mandatory true;
|
||||
case a {
|
||||
leaf udp {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
case b {
|
||||
leaf tcp {
|
||||
type empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg -y $fyang"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg -y $fyang"
|
||||
sudo $clixon_backend -s init -f $cfg -y $fyang
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
fi
|
||||
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u www-data clixon_restconf
|
||||
|
||||
new "start restconf daemon"
|
||||
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data &
|
||||
|
||||
# First vanilla (protocol) case
|
||||
new "netconf validate empty"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf set empty protocol"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf validate protocol"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf set protocol tcp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf get protocol tcp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit protocol tcp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><udp nc:operation="create"/></protocol></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get protocol udp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><udp/></protocol></system></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf commit protocol udp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "restconf set protocol tcp"
|
||||
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":null}})" ""
|
||||
|
||||
new2 "restconf get protocol tcp"
|
||||
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" '{"system:system": {"protocol": {"tcp": null}}}
|
||||
'
|
||||
|
||||
new "cli set protocol udp"
|
||||
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol udp" 0 "^$"
|
||||
|
||||
new "cli get protocol udp"
|
||||
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol udp$"
|
||||
|
||||
new "cli delete all"
|
||||
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o delete all" 0 "^$"
|
||||
|
||||
new "commit"
|
||||
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o commit" 0 "^$"
|
||||
|
||||
# Second shorthand (no case)
|
||||
new "netconf set shorthand tcp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><tcp/></shorthand></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get shorthand udp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf validate shorthand"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf discard-changes"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# Third check mandatory
|
||||
new "netconf set empty mandatory"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf get mandatory empty"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory/></system></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf validate mandatory"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>name</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf set mandatory udp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf get mandatory udp"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf validate mandatory"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "Kill restconf daemon"
|
||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
fi
|
||||
|
||||
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
|
||||
sudo clixon_backend -z -f $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err "kill backend"
|
||||
fi
|
||||
|
||||
rm -rf $dir
|
||||
|
|
@ -88,7 +88,7 @@ if [ $BE -ne 0 ]; then
|
|||
fi
|
||||
|
||||
new "leafref base config"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name><type>ex:eth</type> <ipv4><address><ip>192.0.2.1</ip></address><address><ip>192.0.2.2</ip></address></ipv4></interface><interface><name>lo</name><type>ex:lo</type><ipv4><address><ip>127.0.0.1</ip></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name><type>ex:eth</type><ipv4><address><ip>192.0.2.1</ip><prefix-length>24</prefix-length></address><address><ip>192.0.2.2</ip><prefix-length>24</prefix-length></address></ipv4></interface><interface><name>lo</name><type>ex:lo</type><ipv4><address><ip>127.0.0.1</ip><prefix-length>32</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "leafref get config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name>'
|
||||
|
|
|
|||
|
|
@ -115,13 +115,28 @@ expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http://
|
|||
new2 "restconf wrong method"
|
||||
expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}}
'
|
||||
|
||||
new2 "restconf edit-config missing mandatory"
|
||||
new2 "restconf example missing input"
|
||||
expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}}
'
|
||||
|
||||
|
||||
new "netconf kill-session missing session-id mandatory"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
||||
# edit-config?
|
||||
new "netconf edit-config ok"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit-config extra arg should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit-config empty target should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target/><config/></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>config-target</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>
|
||||
$'
|
||||
|
||||
new "netconf edit-config missing target should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-tag>missing-element</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-info><bad-element>target</bad-element></error-info></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit-config missing config should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target></edit-config></rpc>]]>]]>' '<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>edit-content</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
||||
new "Kill restconf daemon"
|
||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue