* Optimized validation of large lists
* New xmldb_get1() returning actual cache - not a copy. This has lead to some householding instead of just deleting the copy * xml_diff rewritten to work linearly instead of O(2) * New xml_insert function using tree search. The new code uses this in insertion xmldb_put and defaults. (Note previous xml_insert renamed to xml_wrap_all)
This commit is contained in:
parent
9b9b53c4ee
commit
c79baf1b1f
16 changed files with 937 additions and 301 deletions
|
|
@ -92,7 +92,10 @@
|
|||
```
|
||||
|
||||
### Minor changes
|
||||
* Optimized validation by making xml_diff work on raw cache tree (not copies)
|
||||
* Optimized validation of large lists
|
||||
* New xmldb_get1() returning actual cache - not a copy. This has lead to some householding instead of just deleting the copy
|
||||
* xml_diff rewritten to work linearly instead of O(2)
|
||||
* New xml_insert function using tree search. The new code uses this in insertion xmldb_put and defaults. (Note previous xml_insert renamed to xml_wrap_all)
|
||||
* Added syntactic check for yang status: current, deprecated or obsolete.
|
||||
* Added `xml_wrap` function that adds an XML node above a node as a wrapper
|
||||
* also renamed `xml_insert` to `xml_wrap_all`.
|
||||
|
|
|
|||
|
|
@ -41,4 +41,6 @@
|
|||
*/
|
||||
#undef RPC_USERNAME_ASSERT
|
||||
|
||||
|
||||
/* Use new xml_insert code on sorted xml lists
|
||||
*/
|
||||
#define USE_XML_INSERT
|
||||
|
|
|
|||
|
|
@ -118,8 +118,9 @@ cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type);
|
|||
cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
|
||||
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type);
|
||||
|
||||
cxobj **xml_childvec_get(cxobj *x);
|
||||
int xml_child_insert_pos(cxobj *x, cxobj *xc, int i);
|
||||
int xml_childvec_set(cxobj *x, int len);
|
||||
cxobj **xml_childvec_get(cxobj *x);
|
||||
cxobj *xml_new(char *name, cxobj *xn_parent, yang_stmt *spec);
|
||||
yang_stmt *xml_spec(cxobj *x);
|
||||
int xml_spec_set(cxobj *x, yang_stmt *spec);
|
||||
|
|
@ -130,7 +131,6 @@ cxobj *xml_find(cxobj *xn_parent, char *name);
|
|||
int xml_addsub(cxobj *xp, cxobj *xc);
|
||||
cxobj *xml_wrap_all(cxobj *xp, char *tag);
|
||||
cxobj *xml_wrap(cxobj *xc, char *tag);
|
||||
#define xml_insert(x,t) xml_wrap_all((x),(t))
|
||||
int xml_purge(cxobj *xc);
|
||||
int xml_child_rm(cxobj *xp, int i);
|
||||
int xml_rm(cxobj *xc);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@
|
|||
* Prototypes
|
||||
*/
|
||||
int xml_child_spec(cxobj *x, cxobj *xp, yang_stmt *yspec, yang_stmt **yp);
|
||||
int xml_cmp(cxobj *x1, cxobj *x2, int enm);
|
||||
int xml_sort(cxobj *x0, void *arg);
|
||||
int xml_insert(cxobj *xp, cxobj *xc);
|
||||
int xml_sort_verify(cxobj *x, void *arg);
|
||||
int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp);
|
||||
|
||||
|
|
|
|||
|
|
@ -129,8 +129,6 @@ text_modify(clicon_handle h,
|
|||
int ret;
|
||||
int changed = 0; /* Only if x0p's children have changed-> sort is necessary */
|
||||
|
||||
assert(x1 && xml_type(x1) == CX_ELMNT);
|
||||
assert(y0);
|
||||
/* Check for operations embedded in tree according to netconf */
|
||||
if ((opstr = xml_find_value(x1, "operation")) != NULL)
|
||||
if (xml_operation(opstr, &op) < 0)
|
||||
|
|
@ -157,8 +155,16 @@ text_modify(clicon_handle h,
|
|||
permit = 1;
|
||||
}
|
||||
// int iamkey=0;
|
||||
|
||||
#ifdef USE_XML_INSERT
|
||||
/* Add new xml node but without parent - insert when node fully
|
||||
copied (see changed conditional below) */
|
||||
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
#else
|
||||
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
#endif
|
||||
changed++;
|
||||
|
||||
/* Copy xmlns attributes */
|
||||
|
|
@ -204,6 +210,12 @@ text_modify(clicon_handle h,
|
|||
}
|
||||
}
|
||||
}
|
||||
#ifdef USE_XML_INSERT
|
||||
if (changed){
|
||||
if (xml_insert(x0p, x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case OP_DELETE:
|
||||
if (x0==NULL){
|
||||
|
|
@ -282,8 +294,15 @@ text_modify(clicon_handle h,
|
|||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
#ifdef USE_XML_INSERT
|
||||
/* Add new xml node but without parent - insert when node fully
|
||||
copied (see changed conditional below) */
|
||||
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
#else
|
||||
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
||||
goto done;
|
||||
#endif
|
||||
changed++;
|
||||
/* Copy xmlns attributes */
|
||||
x1a = NULL;
|
||||
|
|
@ -345,6 +364,12 @@ text_modify(clicon_handle h,
|
|||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
#ifdef USE_XML_INSERT
|
||||
if (changed){
|
||||
if (xml_insert(x0p, x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case OP_DELETE:
|
||||
if (x0==NULL){
|
||||
|
|
@ -362,15 +387,16 @@ text_modify(clicon_handle h,
|
|||
}
|
||||
if (xml_purge(x0) < 0)
|
||||
goto done;
|
||||
changed++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} /* CONTAINER switch op */
|
||||
} /* else Y_CONTAINER */
|
||||
#ifndef USE_XML_INSERT
|
||||
if (changed)
|
||||
xml_sort(x0p, NULL);
|
||||
#endif
|
||||
retval = 1;
|
||||
done:
|
||||
if (x0vec)
|
||||
|
|
@ -417,8 +443,8 @@ text_modify_top(clicon_handle h,
|
|||
int ret;
|
||||
|
||||
/* Assure top-levels are 'config' */
|
||||
assert(x0 && strcmp(xml_name(x0),"config")==0);
|
||||
assert(x1 && strcmp(xml_name(x1),"config")==0);
|
||||
// assert(x0 && strcmp(xml_name(x0),"config")==0);
|
||||
// assert(x1 && strcmp(xml_name(x1),"config")==0);
|
||||
|
||||
/* Check for operations embedded in tree according to netconf */
|
||||
if ((opstr = xml_find_value(x1, "operation")) != NULL)
|
||||
|
|
|
|||
|
|
@ -915,79 +915,4 @@ json_parse_file(int fd,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn this on to get a json parse and pretty print test program
|
||||
* Usage: json
|
||||
* read json from input
|
||||
* Example compile:
|
||||
gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen
|
||||
* Example run:
|
||||
echo '{"foo": -23}' | ./json
|
||||
*/
|
||||
#if 0 /* Test program */
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc,
|
||||
char **argv)
|
||||
{
|
||||
cxobj *xt;
|
||||
cxobj *xc;
|
||||
cbuf *cb = cbuf_new();
|
||||
char *buf = NULL;
|
||||
int i;
|
||||
int c;
|
||||
int len;
|
||||
FILE *f = stdin;
|
||||
|
||||
if (argc != 1){
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
|
||||
len = 1024; /* any number is fine */
|
||||
if ((buf = malloc(len)) == NULL){
|
||||
perror("malloc");
|
||||
return -1;
|
||||
}
|
||||
memset(buf, 0, len);
|
||||
|
||||
i = 0; /* position in buf */
|
||||
while (1){ /* read the whole file */
|
||||
if ((c = fgetc(f)) == EOF)
|
||||
break;
|
||||
if (len==i){
|
||||
if ((buf = realloc(buf, 2*len)) == NULL){
|
||||
fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
memset(buf+len, 0, len);
|
||||
len *= 2;
|
||||
}
|
||||
buf[i++] = (char)(c&0xff);
|
||||
} /* read a line */
|
||||
|
||||
if (json_parse_str(buf, &xt) < 0)
|
||||
return -1;
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
|
||||
xmltree2cbuf(cb, xc, 0); /* dump data structures */
|
||||
//clicon_xml2cbuf(cb, xc, 0, 1); /* print xml */
|
||||
}
|
||||
fprintf(stdout, "%s", cbuf_get(cb));
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* Test program */
|
||||
|
||||
|
|
|
|||
|
|
@ -562,6 +562,7 @@ xml_child_nr_type(cxobj *xn,
|
|||
* @param[in] i the number of the child, eg order in children vector
|
||||
* @retval xml The child xml node
|
||||
* @retval NULL if no such child, or empty child
|
||||
* @see xml_child_i_type
|
||||
*/
|
||||
cxobj *
|
||||
xml_child_i(cxobj *xn,
|
||||
|
|
@ -652,7 +653,7 @@ xml_child_each(cxobj *xparent,
|
|||
}
|
||||
|
||||
/*! Extend child vector with one and insert xml node there
|
||||
* Note: does not do anything with child, you may need to set its parent, etc
|
||||
* @note does not do anything with child, you may need to set its parent, etc
|
||||
*/
|
||||
static int
|
||||
xml_child_append(cxobj *x,
|
||||
|
|
@ -668,7 +669,31 @@ xml_child_append(cxobj *x,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Set a a childvec to a specific size, fill with children after
|
||||
/*! Insert child xc at position i under parent xp
|
||||
*
|
||||
* @see xml_child_append
|
||||
* @note does not do anything with child, you may need to set its parent, etc
|
||||
*/
|
||||
int
|
||||
xml_child_insert_pos(cxobj *xp,
|
||||
cxobj *xc,
|
||||
int i)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
xp->x_childvec_len++;
|
||||
xp->x_childvec = realloc(xp->x_childvec, xp->x_childvec_len*sizeof(cxobj*));
|
||||
if (xp->x_childvec == NULL){
|
||||
clicon_err(OE_XML, errno, "realloc");
|
||||
return -1;
|
||||
}
|
||||
size = (xml_child_nr(xp) - i - 1)*sizeof(cxobj *);
|
||||
memmove(&xp->x_childvec[i+1], &xp->x_childvec[i], size);
|
||||
xp->x_childvec[i] = xc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Set a childvec to a specific size, fill with children after
|
||||
* @code
|
||||
* xml_childvec_set(x, 2);
|
||||
* xml_child_i_set(x, 0, xc0)
|
||||
|
|
@ -731,6 +756,7 @@ xml_new(char *name,
|
|||
xml_parent_set(x, xp);
|
||||
if (xml_child_append(xp, x) < 0)
|
||||
return NULL;
|
||||
x->_x_i = xml_child_nr(xp)-1;
|
||||
}
|
||||
x->x_spec = yspec; /* Can be NULL */
|
||||
return x;
|
||||
|
|
@ -966,15 +992,14 @@ xml_child_rm(cxobj *xp,
|
|||
int
|
||||
xml_rm(cxobj *xc)
|
||||
{
|
||||
int retval = 0;
|
||||
int retval = -1;
|
||||
cxobj *xp;
|
||||
cxobj *x;
|
||||
int i;
|
||||
|
||||
if ((xp = xml_parent(xc)) == NULL)
|
||||
goto done;
|
||||
retval = -1;
|
||||
/* Find child in parent */
|
||||
goto ok;
|
||||
/* Find child in parent XXX: search? */
|
||||
x = NULL; i = 0;
|
||||
while ((x = xml_child_each(xp, x, -1)) != NULL) {
|
||||
if (x == xc)
|
||||
|
|
@ -982,7 +1007,10 @@ xml_rm(cxobj *xc)
|
|||
i++;
|
||||
}
|
||||
if (x != NULL)
|
||||
retval = xml_child_rm(xp, i);
|
||||
if (xml_child_rm(xp, i) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1060,6 +1060,13 @@ cvec2xml_1(cvec *cvv,
|
|||
* @param[out] changed_x0 Pointervector to XML nodes changed orig value
|
||||
* @param[out] changed_x1 Pointervector to XML nodes changed wanted value
|
||||
* @param[out] changedlen Length of changed vector
|
||||
* Algorithm to compare two sorted lists A, B:
|
||||
* A 0 1 2 3 5 6
|
||||
* B 0 2 4 5 6
|
||||
* Let a,b be first elements of A,B respectively
|
||||
* a = b : recurse; get next a,b
|
||||
* a < b : add a in x0, get next a
|
||||
* a > b : add b in x1, get next b
|
||||
*/
|
||||
static int
|
||||
xml_diff1(yang_stmt *ys,
|
||||
|
|
@ -1079,26 +1086,45 @@ xml_diff1(yang_stmt *ys,
|
|||
yang_stmt *yc;
|
||||
char *b1;
|
||||
char *b2;
|
||||
int eq;
|
||||
|
||||
clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec");
|
||||
/* Check nodes present in x0 and x1 + nodes only in x0
|
||||
* Loop over x0
|
||||
* XXX: room for improvement. Compare with match_base_child()
|
||||
*/
|
||||
x0c = NULL;
|
||||
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL){
|
||||
/* Traverse x0 and x1 in lock-step */
|
||||
x0c = x1c = NULL;
|
||||
x0c = xml_child_each(x0, x0c, CX_ELMNT);
|
||||
x1c = xml_child_each(x1, x1c, CX_ELMNT);
|
||||
for (;;){
|
||||
if (x0c == NULL && x1c == NULL)
|
||||
goto ok;
|
||||
else if (x0c == NULL){
|
||||
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
|
||||
goto done;
|
||||
x1c = xml_child_each(x1, x1c, CX_ELMNT);
|
||||
continue;
|
||||
}
|
||||
else if (x1c == NULL){
|
||||
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
|
||||
goto done;
|
||||
x0c = xml_child_each(x0, x0c, CX_ELMNT);
|
||||
continue;
|
||||
}
|
||||
/* Both x0c and x1c exists, check if they are equal. */
|
||||
eq = xml_cmp(x0c, x1c, 0);
|
||||
if (eq < 0){
|
||||
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
|
||||
goto done;
|
||||
x0c = xml_child_each(x0, x0c, CX_ELMNT);
|
||||
}
|
||||
else if (eq > 0){
|
||||
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
|
||||
goto done;
|
||||
x1c = xml_child_each(x1, x1c, CX_ELMNT);
|
||||
}
|
||||
else{ /* equal */
|
||||
if ((yc = xml_spec(x0c)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x0c));
|
||||
goto done;
|
||||
}
|
||||
/* Does x1 have a child matching x0c? */
|
||||
if (match_base_child(x1, x0c, yc, &x1c) < 0)
|
||||
goto done;
|
||||
if (x1c == NULL){
|
||||
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (yang_choice(yc)){
|
||||
if (yang_choice(yc)){
|
||||
/* if x0c and x1c are choice/case, then they are changed */
|
||||
if (cxvec_append(x0c, changed_x0, changedlen) < 0)
|
||||
goto done;
|
||||
|
|
@ -1106,8 +1132,8 @@ xml_diff1(yang_stmt *ys,
|
|||
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* if x0c and x1c are leafs w bodies, then they are changed */
|
||||
if (yc->ys_keyword == Y_LEAF){
|
||||
else if (yc->ys_keyword == Y_LEAF){
|
||||
/* if x0c and x1c are leafs w bodies, then they are changed */
|
||||
if ((b1 = xml_body(x0c)) == NULL) /* empty type */
|
||||
break;
|
||||
if ((b2 = xml_body(x1c)) == NULL) /* empty type */
|
||||
|
|
@ -1120,29 +1146,16 @@ xml_diff1(yang_stmt *ys,
|
|||
goto done;
|
||||
}
|
||||
}
|
||||
if (xml_diff1(yc, x0c, x1c,
|
||||
else if (xml_diff1(yc, x0c, x1c,
|
||||
x0vec, x0veclen,
|
||||
x1vec, x1veclen,
|
||||
changed_x0, changed_x1, changedlen)< 0)
|
||||
goto done;
|
||||
}
|
||||
} /* while x0 */
|
||||
/* Check nodes present only in x1
|
||||
* Loop over x1
|
||||
*/
|
||||
x1c = NULL;
|
||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){
|
||||
if ((yc = xml_spec(x1c)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c));
|
||||
goto done;
|
||||
x0c = xml_child_each(x0, x0c, CX_ELMNT);
|
||||
x1c = xml_child_each(x1, x1c, CX_ELMNT);
|
||||
}
|
||||
/* Does x0 have a child matching x1c? */
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
if (x0c == NULL)
|
||||
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
|
||||
goto done;
|
||||
} /* while x0 */
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -1622,6 +1635,7 @@ xml_default(cxobj *xt,
|
|||
cxobj *xc;
|
||||
cxobj *xb;
|
||||
char *str;
|
||||
int added=0;
|
||||
|
||||
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
|
||||
retval = 0;
|
||||
|
|
@ -1637,8 +1651,14 @@ xml_default(cxobj *xt,
|
|||
assert(y->ys_cv);
|
||||
if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */
|
||||
if (!xml_find(xt, y->ys_argument)){
|
||||
|
||||
#ifdef USE_XML_INSERT
|
||||
if ((xc = xml_new(y->ys_argument, NULL, y)) == NULL)
|
||||
goto done;
|
||||
#else
|
||||
if ((xc = xml_new(y->ys_argument, xt, y)) == NULL)
|
||||
goto done;
|
||||
#endif
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, NULL)) == NULL)
|
||||
goto done;
|
||||
|
|
@ -1650,11 +1670,19 @@ xml_default(cxobj *xt,
|
|||
if (xml_value_set(xb, str) < 0)
|
||||
goto done;
|
||||
free(str);
|
||||
added++;
|
||||
#ifdef USE_XML_INSERT
|
||||
if (xml_insert(xt, xc) < 0)
|
||||
goto done;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef USE_XML_INSERT
|
||||
if (added)
|
||||
xml_sort(xt, NULL);
|
||||
#endif
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -188,19 +188,22 @@ xml_child_spec(cxobj *x,
|
|||
}
|
||||
|
||||
/*! Help function to qsort for sorting entries in xml child vector same parent
|
||||
* @param[in] xml object 1
|
||||
* @param[in] xml object 2
|
||||
* @param[in] x1 object 1
|
||||
* @param[in] x2 object 2
|
||||
* @param[in] same If set, x1 and x2 are member of same parent & enumeration
|
||||
* is used
|
||||
* @retval 0 If equal
|
||||
* @retval <0 if x1 is less than x2
|
||||
* @retval >0 if x1 is greater than x2
|
||||
* @retval <0 If x1 is less than x2
|
||||
* @retval >0 If x1 is greater than x2
|
||||
* @see xml_cmp1 Similar, but for one object
|
||||
* @note empty value/NULL is smallest value
|
||||
* @note xml_enumerate_children must have been called prior to this call
|
||||
* @note some error cases return as -1 (qsort cant handle errors)
|
||||
*/
|
||||
static int
|
||||
int
|
||||
xml_cmp(cxobj *x1,
|
||||
cxobj *x2)
|
||||
cxobj *x2,
|
||||
int same)
|
||||
{
|
||||
yang_stmt *y1;
|
||||
yang_stmt *y2;
|
||||
|
|
@ -218,17 +221,17 @@ xml_cmp(cxobj *x1,
|
|||
int nr2 = 0;
|
||||
cxobj *x1b;
|
||||
cxobj *x2b;
|
||||
int e;
|
||||
|
||||
e=0;
|
||||
if (x1==NULL || x2==NULL)
|
||||
goto done; /* shouldnt happen */
|
||||
e=1;
|
||||
y1 = xml_spec(x1);
|
||||
y2 = xml_spec(x2);
|
||||
if (same){
|
||||
nr1 = xml_enumerate_get(x1);
|
||||
nr2 = xml_enumerate_get(x2);
|
||||
}
|
||||
if (y1==NULL && y2==NULL){
|
||||
if (same)
|
||||
equal = nr1-nr2;
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -240,24 +243,22 @@ xml_cmp(cxobj *x1,
|
|||
equal = 1;
|
||||
goto done;
|
||||
}
|
||||
e=2;
|
||||
if (y1 != y2){
|
||||
yi1 = yang_order(y1);
|
||||
yi2 = yang_order(y2);
|
||||
if ((equal = yi1-yi2) != 0)
|
||||
goto done;
|
||||
}
|
||||
e=3;
|
||||
/* Now y1==y2, same Yang spec, can only be list or leaf-list,
|
||||
* But first check exceptions, eg config false or ordered-by user
|
||||
* otherwise sort according to key
|
||||
*/
|
||||
if (yang_config(y1)==0 ||
|
||||
yang_find(y1, Y_ORDERED_BY, "user") != NULL){
|
||||
if (same)
|
||||
equal = nr1-nr2;
|
||||
goto done; /* Ordered by user or state data : maintain existing order */
|
||||
}
|
||||
e=4;
|
||||
switch (y1->ys_keyword){
|
||||
case Y_LEAF_LIST: /* Match with name and value */
|
||||
if ((b1 = xml_body(x1)) == NULL)
|
||||
|
|
@ -299,9 +300,8 @@ xml_cmp(cxobj *x1,
|
|||
default:
|
||||
break;
|
||||
}
|
||||
e=5;
|
||||
done:
|
||||
clicon_debug(2, "%s %s %s %d %d nr: %d %d yi: %d %d", __FUNCTION__, xml_name(x1), xml_name(x2), equal, e, nr1, nr2, yi1, yi2);
|
||||
clicon_debug(2, "%s %s %s %d nr: %d %d yi: %d %d", __FUNCTION__, xml_name(x1), xml_name(x2), equal, nr1, nr2, yi1, yi2);
|
||||
return equal;
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ static int
|
|||
xml_cmp_qsort(const void* arg1,
|
||||
const void* arg2)
|
||||
{
|
||||
return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2);
|
||||
return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1);
|
||||
}
|
||||
|
||||
/*! Compare xml object
|
||||
|
|
@ -531,76 +531,151 @@ xml_search(cxobj *x0,
|
|||
{
|
||||
cxobj *xa;
|
||||
int low = 0;
|
||||
int high = xml_child_nr(x0);
|
||||
int upper = xml_child_nr(x0);
|
||||
|
||||
/* Assume if there are any attributes, they are first in the list, mask
|
||||
them by raising low to skip them */
|
||||
for (low=0; low<high; low++)
|
||||
for (low=0; low<upper; low++)
|
||||
if ((xa = xml_child_i(x0, low)) == NULL || xml_type(xa)!=CX_ATTR)
|
||||
break;
|
||||
return xml_search1(x0, name, yangi, keyword, keynr, keyvec, keyval, keycvec,
|
||||
low, high);
|
||||
low, upper);
|
||||
}
|
||||
|
||||
#ifdef NOTUSED
|
||||
/*! Position where to insert xml object into a list of children nodes
|
||||
* @note EXPERIMENTAL
|
||||
* Insert after position returned
|
||||
* @param[in] x0 XML parent node.
|
||||
* @param[in] low Lower bound
|
||||
* @param[in] upper Upper bound (+1)
|
||||
* @retval position
|
||||
* XXX: Problem with this is that evrything must be known before insertion
|
||||
/*! Insert xn in xp:s sorted child list
|
||||
* Find a point in xp childvec with two adjacent nodes xi,xi+1 such that
|
||||
* xi<=xn<=xi+1 or xn<=x0 or xmax<=xn
|
||||
*/
|
||||
int
|
||||
xml_insert_pos(cxobj *x0,
|
||||
char *name,
|
||||
int yangi,
|
||||
enum rfc_6020 keyword,
|
||||
int keynr,
|
||||
char **keyvec,
|
||||
char **keyval,
|
||||
static int
|
||||
xml_insert2(cxobj *xp,
|
||||
cxobj *xn,
|
||||
yang_stmt *yn,
|
||||
int yni,
|
||||
int userorder,
|
||||
int low,
|
||||
int upper)
|
||||
{
|
||||
int retval = -1;
|
||||
int mid;
|
||||
cxobj *xc;
|
||||
yang_stmt *y;
|
||||
int cmp;
|
||||
cxobj *xc;
|
||||
yang_stmt *yc;
|
||||
int i;
|
||||
int userorder= 0;
|
||||
|
||||
if (upper < low)
|
||||
return low; /* not found */
|
||||
if (low > upper){ /* beyond range */
|
||||
clicon_err(OE_XML, 0, "low>upper %d %d", low, upper);
|
||||
goto done;
|
||||
}
|
||||
if (low == upper){
|
||||
retval = low;
|
||||
goto done;
|
||||
}
|
||||
mid = (low + upper) / 2;
|
||||
if (mid >= xml_child_nr(x0))
|
||||
return xml_child_nr(x0); /* upper range */
|
||||
xc = xml_child_i(x0, mid);
|
||||
y = xml_spec(xc);
|
||||
cmp = yangi-yang_order(y);
|
||||
if (mid >= xml_child_nr(xp)){ /* beyond range */
|
||||
clicon_err(OE_XML, 0, "Beyond range %d %d %d", low, mid, upper);
|
||||
goto done;
|
||||
}
|
||||
xc = xml_child_i(xp, mid);
|
||||
if ((yc = xml_spec(xc)) == NULL){
|
||||
clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc));
|
||||
goto done;
|
||||
}
|
||||
if (yc == yn){ /* Same yang */
|
||||
if (userorder){ /* append: increment linearly until no longer equal */
|
||||
for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
|
||||
xc = xml_child_i(xp, i);
|
||||
yc = xml_spec(xc);
|
||||
if (yc != yn){
|
||||
retval = i;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
retval = i;
|
||||
goto done;
|
||||
}
|
||||
else /* Ordered by system */
|
||||
cmp = xml_cmp(xn, xc, 0);
|
||||
}
|
||||
else{ /* Not equal yang - compute diff */
|
||||
cmp = yni - yang_order(yc);
|
||||
/* One case is a choice where
|
||||
* xc = <tcp/>, xn = <udp/>
|
||||
* same order but different yang spec
|
||||
*/
|
||||
}
|
||||
if (low +1 == upper){ /* termination criterium */
|
||||
#if 0
|
||||
if (xml_child_nr(xp) <= mid+1){
|
||||
retval = mid;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
if (cmp<0) {
|
||||
retval = mid;
|
||||
goto done;
|
||||
}
|
||||
retval = mid+1;
|
||||
goto done;
|
||||
}
|
||||
if (cmp == 0){
|
||||
cmp = xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, keycvec, &userorder);
|
||||
if (userorder){ /* Look inside this yangi order */
|
||||
/* Special case: append last of equals if ordered by user */
|
||||
for (i=mid+1;i<xml_child_nr(x0);i++){
|
||||
xc = xml_child_i(x0, i);
|
||||
if (strcmp(xml_name(xc), name))
|
||||
break;
|
||||
mid=i; /* still ok */
|
||||
retval = mid;
|
||||
goto done;
|
||||
}
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
if (cmp == 0)
|
||||
return mid;
|
||||
else if (cmp < 0)
|
||||
return xml_insert_pos(x0, name, yangi, keyword,
|
||||
keynr, keyvec, keyval, keycvec, low, mid-1);
|
||||
return xml_insert2(xp, xn, yn, yni, userorder, low, mid);
|
||||
else
|
||||
return xml_insert_pos(x0, name, yangi, keyword,
|
||||
keynr, keyvec, keyval, keycvec, mid+1, upper);
|
||||
return xml_insert2(xp, xn, yn, yni, userorder, mid+1, upper);
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Insert xc as child to xp in sorted place. Remove xc from previous parent.
|
||||
* @param[in] xp Parent xml node. If NULL just remove from old parent.
|
||||
* @param[in] x Child xml node to insert under xp
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
|
||||
*/
|
||||
int
|
||||
xml_insert(cxobj *xp,
|
||||
cxobj *xi)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xa;
|
||||
int low = 0;
|
||||
int upper;
|
||||
yang_stmt *y;
|
||||
int userorder= 0;
|
||||
int yi; /* Global yang-stmt order */
|
||||
int i;
|
||||
|
||||
/* Ensure the intermediate state that xp is parent of x but has not yet been
|
||||
* added as a child
|
||||
*/
|
||||
// assert(xml_parent(xi) == NULL);
|
||||
// assert(y = xml_spec(xi));
|
||||
|
||||
upper = xml_child_nr(xp);
|
||||
/* Assume if there are any attributes, they are first in the list, mask
|
||||
them by raising low to skip them */
|
||||
for (low=0; low<upper; low++)
|
||||
if ((xa = xml_child_i(xp, low)) == NULL || xml_type(xa)!=CX_ATTR)
|
||||
break;
|
||||
/* Find if non-config and if ordered-by-user */
|
||||
if (yang_config(y)==0)
|
||||
userorder = 1;
|
||||
else if (y->ys_keyword == Y_LIST || y->ys_keyword == Y_LEAF_LIST)
|
||||
userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL);
|
||||
yi = yang_order(y);
|
||||
if ((i = xml_insert2(xp, xi, y, yi, userorder, low, upper)) < 0)
|
||||
goto done;
|
||||
if (xml_child_insert_pos(xp, xi, i) < 0)
|
||||
goto done;
|
||||
xml_parent_set(xi, xp);
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
#endif /* NOTUSED */
|
||||
|
||||
/*! Verify all children of XML node are sorted according to xml_sort()
|
||||
* @param[in] x XML node. Check its children
|
||||
|
|
@ -626,7 +701,7 @@ xml_sort_verify(cxobj *x0,
|
|||
xml_enumerate_children(x0);
|
||||
while ((x = xml_child_each(x0, x, -1)) != NULL) {
|
||||
if (xprev != NULL){ /* Check xprev <= x */
|
||||
if (xml_cmp(xprev, x) > 0)
|
||||
if (xml_cmp(xprev, x, 1) > 0)
|
||||
goto done;
|
||||
}
|
||||
xprev = x;
|
||||
|
|
|
|||
|
|
@ -660,6 +660,30 @@ yang_choice(yang_stmt *y)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
order1_choice(yang_stmt *yp,
|
||||
yang_stmt *y)
|
||||
{
|
||||
yang_stmt *ys;
|
||||
yang_stmt *yc;
|
||||
int i;
|
||||
int j;
|
||||
|
||||
for (i=0; i<yp->ys_len; i++){
|
||||
ys = yp->ys_stmt[i];
|
||||
if (ys->ys_keyword == Y_CASE){
|
||||
for (j=0; j<ys->ys_len; j++){
|
||||
yc = ys->ys_stmt[j];
|
||||
if (yang_datanode(yc) && yc == y)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (yang_datanode(ys) && ys == y)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
|
||||
* @param[in] yp Parent
|
||||
* @param[in] y Yang datanode to find
|
||||
|
|
@ -677,10 +701,16 @@ order1(yang_stmt *yp,
|
|||
|
||||
for (i=0; i<yp->ys_len; i++){
|
||||
ys = yp->ys_stmt[i];
|
||||
if (ys->ys_keyword == Y_CHOICE){
|
||||
if (order1_choice(ys, y) == 1) /* If one of the choices is "y" */
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
if (!yang_datanode(ys))
|
||||
continue;
|
||||
if (ys==y)
|
||||
return 1;
|
||||
}
|
||||
(*index)++;
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -703,7 +733,14 @@ yang_order(yang_stmt *y)
|
|||
int j=0;
|
||||
int tot = 0;
|
||||
|
||||
/* Some special handling if yp is choice (or case) and maybe union?
|
||||
* if so, the real parent (from an xml point of view) is the parents
|
||||
* parent.
|
||||
*/
|
||||
yp = y->ys_parent;
|
||||
while (yp->ys_keyword == Y_CASE || yp->ys_keyword == Y_CHOICE)
|
||||
yp = yp->ys_parent;
|
||||
|
||||
/* XML nodes with yang specs that are children of modules are special -
|
||||
* In clixon, they are seen as an "implicit" container where the XML can come from different
|
||||
* modules. The order must therefore be global among yang top-symbols to be unique.
|
||||
|
|
@ -1890,6 +1927,7 @@ yang_parse_str(char *str,
|
|||
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
|
||||
* @retval ymod Top-level yang (sub)module
|
||||
* @retval NULL Error
|
||||
* @note this function simply parse a yang spec, no dependencies or checks
|
||||
*/
|
||||
yang_stmt *
|
||||
yang_parse_file(int fd,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ The `mem.sh` runs memory checks using valgrind. Start it with no arguments to te
|
|||
mem.sh restconf backend # Only backend and cli
|
||||
```
|
||||
|
||||
## Performance plots
|
||||
|
||||
The script `plot_perf.sh` produces gnuplots for some testcases.
|
||||
|
||||
## Site.sh
|
||||
You may add your site-specific modifications in a `site.sh` file. Example:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,10 +1,33 @@
|
|||
#!/bin/bash
|
||||
# Transactions per second for large lists read/write plotter using gnuplot
|
||||
# WORK IN PROGRESS
|
||||
. ./lib.sh
|
||||
max=2000 # Nr of db entries
|
||||
step=200
|
||||
reqs=500
|
||||
# What do I want to plot?
|
||||
# 1. How long to write 100K entries?
|
||||
# - netconf / restconf
|
||||
# - list / leaf-list
|
||||
# 2. How long to read 100K entries?
|
||||
# - netconf/ restconf
|
||||
# - list / leaf-list
|
||||
# 3. How long to commit 100K entries? (netconf)
|
||||
# - list / leaf-list
|
||||
# 4. In database 100K entries. How many read operations per second?
|
||||
# - netconf/ restconf
|
||||
# - list / leaf-list
|
||||
# 5. 100K entries. How many write operations per second?
|
||||
# - netconf / restconf
|
||||
# - list / leaf-list
|
||||
# 6. 100K entries. How may delete operations per second?
|
||||
# - netconf / restconf
|
||||
# - list / leaf-list
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
#max=2000 # Nr of db entries
|
||||
#step=200
|
||||
#reqs=500
|
||||
|
||||
# Global variables
|
||||
APPNAME=example
|
||||
cfg=$dir/plot-conf.xml
|
||||
fyang=$dir/plot.yang
|
||||
fconfig=$dir/config
|
||||
|
|
@ -15,7 +38,10 @@ fconfig=$dir/config
|
|||
clixon_netconf=clixon_netconf
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module ietf-ip{
|
||||
module scaling{
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix sc;
|
||||
container x {
|
||||
list y {
|
||||
key "a";
|
||||
|
|
@ -34,23 +60,32 @@ module ietf-ip{
|
|||
EOF
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<config>
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>$fyang</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>ietf-ip</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_SOCK>/usr/local/var/routing/routing.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/routing/routing.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/routing</CLICON_XMLDB_DIR>
|
||||
</config>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>scaling</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
run(){
|
||||
# Run function
|
||||
# args: <i> <reqs> <mode>
|
||||
# where mode is one of:
|
||||
# readlist writelist restreadlist restwritelist
|
||||
runfn(){
|
||||
nr=$1 # Number of entries in DB
|
||||
reqs=$2
|
||||
mode=$3
|
||||
operation=$3
|
||||
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x>" > $fconfig
|
||||
# echo "runfn nr=$nr reqs=$reqs mode=$mode"
|
||||
|
||||
# new "generate config with $nr list entries"
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x xmlns=\"urn:example:clixon\">" > $fconfig
|
||||
for (( i=0; i<$nr; i++ )); do
|
||||
case $mode in
|
||||
readlist|writelist|restreadlist|restwritelist)
|
||||
|
|
@ -64,100 +99,135 @@ run(){
|
|||
|
||||
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
|
||||
|
||||
# new "netconf write $nr entry to backend"
|
||||
expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
case $mode in
|
||||
readlist)
|
||||
# new "netconf GET list $reqs"
|
||||
time -p for (( i=0; i<$reqs; i++ )); do
|
||||
rnd=$(( ( RANDOM % $nr ) ))
|
||||
echo "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=$rnd][b=$rnd]\" /></get-config></rpc>]]>]]>"
|
||||
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||
;;
|
||||
writelist)
|
||||
# new "netconf WRITE list $reqs"
|
||||
time -p for (( i=0; i<$reqs; i++ )); do
|
||||
rnd=$(( ( RANDOM % $nr ) ))
|
||||
echo "<rpc><edit-config><target><candidate/></target><config><x><y><a>$rnd</a><b>$rnd</b></y></x></config></edit-config></rpc>]]>]]>"
|
||||
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><y><a>$rnd</a><b>$rnd</b></y></x></config></edit-config></rpc>]]>]]>"
|
||||
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||
;;
|
||||
restreadlist)
|
||||
# new "restconf GET list $reqs"
|
||||
time -p for (( i=0; i<$reqs; i++ )); do
|
||||
rnd=$(( ( RANDOM % $nr ) ))
|
||||
curl -sSG http://localhost/restconf/data/x/y=$rnd,$rnd > /dev/null
|
||||
curl -sSG http://localhost/restconf/data/scaling:x/y=$rnd > /dev/null
|
||||
done
|
||||
;;
|
||||
writeleaflist)
|
||||
# new "netconf GET leaf-list $reqs"
|
||||
time -p for (( i=0; i<$reqs; i++ )); do
|
||||
rnd=$(( ( RANDOM % $nr ) ))
|
||||
echo "<rpc><edit-config><target><candidate/></target><config><x><c>$rnd</c></x></config></edit-config></rpc>]]>]]>"
|
||||
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><c>$rnd</c></x></config></edit-config></rpc>]]>]]>"
|
||||
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||
;;
|
||||
esac
|
||||
# new "discard test"
|
||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
}
|
||||
|
||||
step(){
|
||||
# Step
|
||||
# args: <i> <reqs> <mode>
|
||||
stepfn(){
|
||||
i=$1
|
||||
mode=$2
|
||||
reqs=$2
|
||||
mode=$3
|
||||
|
||||
>&2 echo "stepfn $mode: i=$i reqs=$reqs"
|
||||
echo -n "" > $fconfig
|
||||
t=$(TEST=%e run $i $reqs $mode 2>&1 | awk '/real/ {print $2}')
|
||||
#TEST=%e run $i $reqs $mode 2>&1
|
||||
t=$(TEST=%e runfn $i $reqs $mode 2>&1 | awk '/real/ {print $2}')
|
||||
#TEST=%e runfn $i $reqs $mode 2>&1
|
||||
# t is time in secs of $reqs -> transactions per second. $reqs
|
||||
p=$(echo "$reqs/$t" | bc -lq)
|
||||
# p is transactions per second.
|
||||
# write to gnuplot file: $dir/$mode
|
||||
echo "$i $p" >> $dir/$mode
|
||||
# echo "m:$mode i:$i t=$t p=$p"
|
||||
}
|
||||
|
||||
# Run once
|
||||
#args: <step> <reqs> <max>
|
||||
once(){
|
||||
# kill old backend (if any)
|
||||
# Input Parameters
|
||||
step=$1
|
||||
reqs=$2
|
||||
max=$3
|
||||
|
||||
echo "oncefn step=$step reqs=$reqs max=$max"
|
||||
new "test params: -f $cfg -y $fyang"
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg -y $fyang
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
|
||||
# start new backend
|
||||
sudo clixon_backend -s init -f $cfg -y $fyang
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
new "start backend -s init -f $cfg -y $fyang"
|
||||
start_backend -s init -f $cfg -y $fyang
|
||||
fi
|
||||
|
||||
# Always as a start
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||
|
||||
new "start restconf daemon"
|
||||
start_restconf -f $cfg -y $fyang
|
||||
|
||||
new "waiting"
|
||||
sleep $RCWAIT
|
||||
|
||||
new "Intial steps as start"
|
||||
for (( i=10; i<=$step; i=i+10 )); do
|
||||
step $i readlist
|
||||
step $i writelist
|
||||
step $i restreadlist
|
||||
step $i writeleaflist
|
||||
stepfn $i $reqs readlist
|
||||
stepfn $i $reqs writelist
|
||||
stepfn $i $reqs restreadlist
|
||||
stepfn $i $reqs writeleaflist
|
||||
done
|
||||
# Actual steps
|
||||
rnd=$(( ( RANDOM % $step ) ))
|
||||
echo "curl -sSG http://localhost/restconf/data/scaling:x/y=$rnd"
|
||||
curl -sSG http://localhost/restconf/data/scaling:x/y=$rnd
|
||||
exit
|
||||
new "Actual steps"
|
||||
for (( i=$step; i<=$max; i=i+$step )); do
|
||||
step $i readlist
|
||||
step $i writelist
|
||||
step $i restreadlist
|
||||
step $i writeleaflist
|
||||
stepfn $i $reqs readlist
|
||||
stepfn $i $reqs writelist
|
||||
stepfn $i $reqs restreadlist
|
||||
stepfn $i $reqs writeleaflist
|
||||
done
|
||||
|
||||
# Check if still alive
|
||||
pid=`pgrep clixon_backend`
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
|
||||
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
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err "kill backend"
|
||||
stop_backend -f $cfg
|
||||
fi
|
||||
}
|
||||
|
||||
once
|
||||
# step=200 reqs=500 max=2000
|
||||
once 200 500 1000
|
||||
|
||||
gnuplot -persist <<EOF
|
||||
set title "Clixon transactions per second r/w large lists" font ",14" textcolor rgbcolor "royalblue"
|
||||
set xlabel "entries"
|
||||
set ylabel "transactions per second"
|
||||
set terminal wxt enhanced title "Clixon transactions " persist raise
|
||||
set terminal x11 enhanced title "Clixon transactions " persist raise
|
||||
plot "$dir/readlist" with linespoints title "read list", "$dir/writelist" with linespoints title "write list", "$dir/writeleaflist" with linespoints title "write leaf-list" , "$dir/restreadlist" with linespoints title "rest get list"
|
||||
EOF
|
||||
|
||||
rm -rf $dir
|
||||
#rm -rf $dir
|
||||
|
||||
|
|
|
|||
215
test/test_insert.sh
Executable file
215
test/test_insert.sh
Executable file
|
|
@ -0,0 +1,215 @@
|
|||
#!/bin/bash
|
||||
# XML Insert unit test
|
||||
# First a list with 0-5 base elements, insert in different places
|
||||
# Second varying yangs: container, leaf, list, leaf-list, choice, user-order list
|
||||
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
: ${clixon_util_insert:=clixon_util_insert}
|
||||
|
||||
OPTS="-D $DBG -s"
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/example.yang
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<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_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_CACHE>true</CLICON_XMLDB_CACHE>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module example {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:example";
|
||||
prefix ex;
|
||||
revision 2019-01-13;
|
||||
container c{
|
||||
list a{
|
||||
key x;
|
||||
leaf x{
|
||||
type int32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
testrun(){
|
||||
x0=$1
|
||||
xi="<c xmlns=\"urn:example:example\">$2</c>"
|
||||
xp=c
|
||||
|
||||
new "random list add leaf-list"
|
||||
# First run sorted (assume this is the refernce == correct)
|
||||
rs=$($clixon_util_insert -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS -s)
|
||||
# Then run actual insert
|
||||
r0=$($clixon_util_insert -y $fyang -x "$xi" -b "$x0" -p $xp $OPTS)
|
||||
# If both are null something is amiss
|
||||
if [ -z "$r0" -a -z "$rs" ]; then
|
||||
err "length of retval is zero"
|
||||
fi
|
||||
# echo "rs:$rs"
|
||||
# echo "r0:$r0"
|
||||
# Check they are equal
|
||||
if [[ "$r0" != "$rs" ]]; then
|
||||
err "$rs" "$r0"
|
||||
fi
|
||||
}
|
||||
|
||||
new "test params: -y $fyang $OPTS"
|
||||
|
||||
# Empty element base list
|
||||
x0='<c xmlns="urn:example:example"></c>'
|
||||
new "empty list"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
# One element base list
|
||||
x0='<c xmlns="urn:example:example"><a><x>99</x></a></c>'
|
||||
new "one element list first"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
new "one element list last"
|
||||
testrun "$x0" "<a><x>100</x></a>"
|
||||
|
||||
# Two element base list
|
||||
x0='<c xmlns="urn:example:example"><a><x>2</x></a><a><x>99</x></a></c>'
|
||||
new "two element list first"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
new "two element list mid"
|
||||
testrun "$x0" "<a><x>12</x></a>"
|
||||
|
||||
new "two element list last"
|
||||
testrun "$x0" "<a><x>3000</x></a>"
|
||||
|
||||
# Three element base list
|
||||
x0='<c xmlns="urn:example:example"><a><x>2</x></a><a><x>99</x></a><a><x>101</x></a></c>'
|
||||
new "three element list first"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
new "three element list second"
|
||||
testrun "$x0" "<a><x>10</x></a>"
|
||||
|
||||
new "three element list third"
|
||||
testrun "$x0" "<a><x>100</x></a>"
|
||||
|
||||
new "three element list last"
|
||||
testrun "$x0" "<a><x>1000</x></a>"
|
||||
|
||||
# Four element base list
|
||||
x0='<c xmlns="urn:example:example"><a><x>2</x></a><a><x>99</x></a><a><x>101</x></a><a><x>200</x></a></c>'
|
||||
|
||||
new "four element list first"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
new "four element list second"
|
||||
testrun "$x0" "<a><x>10</x></a>"
|
||||
|
||||
new "four element list third"
|
||||
testrun "$x0" "<a><x>100</x></a>"
|
||||
|
||||
new "four element list fourth"
|
||||
testrun "$x0" "<a><x>102</x></a>"
|
||||
|
||||
new "four element list last"
|
||||
testrun "$x0" "<a><x>1000</x></a>"
|
||||
|
||||
# Five element base list
|
||||
x0='<c xmlns="urn:example:example"><a><x>2</x></a><a><x>99</x></a><a><x>101</x></a><a><x>200</x></a><a><x>300</x></a></c>'
|
||||
|
||||
new "five element list first"
|
||||
testrun "$x0" "<a><x>1</x></a>"
|
||||
|
||||
new "five element list mid"
|
||||
testrun "$x0" "<a><x>100</x></a>"
|
||||
|
||||
new "five element list last"
|
||||
testrun "$x0" "<a><x>1000</x></a>"
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module example {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:example";
|
||||
prefix ex;
|
||||
revision 2019-01-13;
|
||||
container c{
|
||||
leaf a{
|
||||
type string;
|
||||
}
|
||||
container b{
|
||||
leaf a {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
choice c1{
|
||||
case a{
|
||||
leaf x{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
case b{
|
||||
leaf y{
|
||||
type int32;
|
||||
}
|
||||
}
|
||||
}
|
||||
choice c2{
|
||||
leaf z{
|
||||
type string;
|
||||
}
|
||||
leaf t{
|
||||
type int32;
|
||||
}
|
||||
}
|
||||
list d{
|
||||
key x;
|
||||
leaf x{
|
||||
type int32;
|
||||
}
|
||||
ordered-by user;
|
||||
}
|
||||
leaf-list e{
|
||||
type int32;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Advanced list
|
||||
# Empty base list
|
||||
x0='<c xmlns="urn:example:example"></c>'
|
||||
xp=c
|
||||
new "adv empty list add leaf"
|
||||
testrun "$x0" "<a>leaf</a>"
|
||||
|
||||
new "adv empty list add choice c1"
|
||||
testrun "$x0" "<x>choice1</x>"
|
||||
|
||||
xi='<c xmlns="urn:example:example"><e>33</e></c>'
|
||||
new "adv empty list add leaf-list"
|
||||
testrun "$x0" "<e>33</e>"
|
||||
|
||||
# base list
|
||||
x0='<c xmlns="urn:example:example"><a>leaf</a><x>choice</x><d><x>1</x></d><e>33</e></c>'
|
||||
|
||||
new "adv list add leaf-list"
|
||||
testrun "$x0" "<e>32</e>"
|
||||
|
||||
new "adv list add leaf-list"
|
||||
testrun "$x0" "<e>32</e>"
|
||||
|
||||
rm -rf $dir
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
# Number of list/leaf-list entries in file
|
||||
: ${perfnr:=1000}
|
||||
: ${perfnr:=2000}
|
||||
|
||||
# Number of requests made get/put
|
||||
: ${perfreq:=100}
|
||||
|
|
@ -15,6 +15,7 @@ APPNAME=example
|
|||
cfg=$dir/scaling-conf.xml
|
||||
fyang=$dir/scaling.yang
|
||||
fconfig=$dir/large.xml
|
||||
fconfig2=$dir/large2.xml
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module scaling{
|
||||
|
|
@ -43,12 +44,11 @@ cat <<EOF > $cfg
|
|||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>scaling</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
|
@ -90,9 +90,6 @@ expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfi
|
|||
new "netconf write large config again"
|
||||
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# Remove the file, its used for different purposes further down
|
||||
rm $fconfig
|
||||
|
||||
# Now commit it from candidate to running
|
||||
new "netconf commit large config"
|
||||
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
@ -119,7 +116,7 @@ done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
|||
new "restconf get $perfreq small config"
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd,$rnd > /dev/null
|
||||
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd > /dev/null
|
||||
done
|
||||
|
||||
new "restconf add $perfreq small config"
|
||||
|
|
@ -135,19 +132,23 @@ expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get
|
|||
new "restconf get large config"
|
||||
expecteof "/usr/bin/time -f %e curl -sG http://localhost/restconf/data" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^{"data": {"scaling:x": {"y": \[{"a": 0,"b": 0},{ "a": 1,"b": 1},{ "a": 2,"b": 2},{ "a": 3,"b": 3},'
|
||||
|
||||
new "restconf delete $perfreq small config"
|
||||
time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
curl -s -X DELETE http://localhost/restconf/data/scaling:x/y=$rnd
|
||||
done
|
||||
|
||||
# Now do leaf-lists istead of leafs
|
||||
|
||||
new "generate large leaf-list config"
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x xmlns=\"urn:example:clixon\">" > $fconfig
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x xmlns=\"urn:example:clixon\">" > $fconfig2
|
||||
for (( i=0; i<$perfnr; i++ )); do
|
||||
echo -n "<c>$i</c>" >> $fconfig
|
||||
echo -n "<c>$i</c>" >> $fconfig2
|
||||
done
|
||||
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
|
||||
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig2
|
||||
|
||||
new "netconf replace large list-leaf config"
|
||||
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
rm $fconfig
|
||||
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig2" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf commit large leaf-list config"
|
||||
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ APPSRC += clixon_util_json.c
|
|||
APPSRC += clixon_util_yang.c
|
||||
APPSRC += clixon_util_xpath.c
|
||||
APPSRC += clixon_util_datastore.c
|
||||
APPSRC += clixon_util_insert.c
|
||||
ifeq ($(with_restconf),yes)
|
||||
APPSRC += clixon_util_stream.c # Needs curl
|
||||
endif
|
||||
|
|
@ -107,6 +108,9 @@ clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
|
|||
clixon_util_datastore: clixon_util_datastore.c $(LIBDEPS)
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||
|
||||
clixon_util_insert: clixon_util_insert.c $(LIBDEPS)
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
|
|
|
|||
215
util/clixon_util_insert.c
Normal file
215
util/clixon_util_insert.c
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
See https://www.w3.org/TR/xpath/
|
||||
|
||||
* Turn this on to get an xpath test program
|
||||
* Usage: xpath [<xpath>]
|
||||
* read xpath on first line and xml on rest of lines from input
|
||||
* Example compile:
|
||||
gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen
|
||||
* Example run:
|
||||
echo "a\n<a><b/></a>" | xpath
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <fnmatch.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <syslog.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include "clixon/clixon.h"
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:%s [options]\n"
|
||||
"where options are\n"
|
||||
"\t-h \t\tHelp\n"
|
||||
"\t-D <level>\tDebug\n"
|
||||
"\t-y <file> \tYANG spec file (or stdin)\n"
|
||||
"\t-b <base> \tXML base expression\n"
|
||||
"\t-x <xml> \tXML to insert\n"
|
||||
"\t-p <xpath>\tXpath to where in base and XML\n"
|
||||
"\t-s \tSort output after insert\n"
|
||||
"Assume insert xml is first child of xpath. Ie if xml=<a><x>23 and xpath=a, then inserted element is <x>23\n",
|
||||
argv0
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
char *argv0 = argv[0];
|
||||
int c;
|
||||
char *filename = NULL;
|
||||
int fd = 0; /* unless overriden by argv[1] */
|
||||
char *x0str = NULL;
|
||||
char *xistr = NULL;
|
||||
char *xpath = NULL;
|
||||
yang_stmt *yspec;
|
||||
cxobj *x0 = NULL;
|
||||
cxobj *xb;
|
||||
cxobj *xi = NULL;
|
||||
int sort = 0;
|
||||
clicon_handle h;
|
||||
|
||||
clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR);
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
goto done;
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, "hD:y:b:x:p:s")) != -1)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv0);
|
||||
break;
|
||||
case 'D':
|
||||
if (sscanf(optarg, "%d", &debug) != 1)
|
||||
usage(argv0);
|
||||
break;
|
||||
case 'y': /* YANG spec file */
|
||||
filename = optarg;
|
||||
if (0 && (fd = open(filename, O_RDONLY)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "open(%s)", argv[1]);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case 'b': /* Base XML expression */
|
||||
x0str = optarg;
|
||||
break;
|
||||
case 'x': /* XML to insert */
|
||||
xistr = optarg;
|
||||
break;
|
||||
case 'p': /* XPATH base */
|
||||
xpath = optarg;
|
||||
break;
|
||||
case 's': /* sort output after insert */
|
||||
sort++;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
}
|
||||
if (xistr == NULL || x0str == NULL)
|
||||
usage(argv0);
|
||||
if (xpath == NULL)
|
||||
usage(argv0);
|
||||
if (filename == NULL)
|
||||
usage(argv0);
|
||||
clicon_debug(1, "xistr:%s", xistr);
|
||||
clicon_debug(1, "x0str:%s", x0str);
|
||||
clicon_debug(1, "xpath:%s", xpath);
|
||||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
#if 1
|
||||
if (yang_spec_parse_file(h, filename, yspec) < 0)
|
||||
goto done;
|
||||
#else
|
||||
if (yang_parse_file(fd, "yang test", yspec) == NULL)
|
||||
goto done;
|
||||
#endif
|
||||
/* Parse base XML */
|
||||
if (xml_parse_string(x0str, yspec, &x0) < 0){
|
||||
clicon_err(OE_XML, 0, "Parsing base xml: %s", x0str);
|
||||
goto done;
|
||||
}
|
||||
if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
if ((xb = xpath_first(x0, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath);
|
||||
goto done;
|
||||
}
|
||||
if (debug){
|
||||
clicon_debug(1, "xb:");
|
||||
xml_print(stderr, xb);
|
||||
}
|
||||
/* Parse insert XML */
|
||||
if (xml_parse_string(xistr, yspec, &xi) < 0){
|
||||
clicon_err(OE_XML, 0, "Parsing insert xml: %s", xistr);
|
||||
goto done;
|
||||
}
|
||||
if (xml_apply(xi, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
if ((xi = xpath_first(xi, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
|
||||
goto done;
|
||||
}
|
||||
/* Find first element child */
|
||||
if ((xi = xml_child_i_type(xi, 0, CX_ELMNT)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xi has no element child");
|
||||
goto done;
|
||||
}
|
||||
/* Remove it from parent */
|
||||
if (xml_rm(xi) < 0)
|
||||
goto done;
|
||||
if (debug){
|
||||
clicon_debug(1, "xi:");
|
||||
xml_print(stderr, xi);
|
||||
}
|
||||
if (xml_insert(xb, xi) < 0)
|
||||
goto done;
|
||||
if (debug){
|
||||
clicon_debug(1, "x0:");
|
||||
xml_print(stderr, x0);
|
||||
}
|
||||
if (sort)
|
||||
xml_sort(xb, NULL);
|
||||
clicon_xml2file(stdout, xb, 0, 0);
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
if (x0)
|
||||
xml_free(x0);
|
||||
if (yspec)
|
||||
yspec_free(yspec);
|
||||
if (fd > 0)
|
||||
close(fd);
|
||||
return retval;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue