* 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:
Olof hagsand 2019-04-14 14:36:41 +02:00
parent 9b9b53c4ee
commit c79baf1b1f
16 changed files with 937 additions and 301 deletions

View file

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

View file

@ -41,4 +41,6 @@
*/
#undef RPC_USERNAME_ASSERT
/* Use new xml_insert code on sorted xml lists
*/
#define USE_XML_INSERT

View file

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

View file

@ -40,8 +40,10 @@
* 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_sort_verify(cxobj *x, void *arg);
int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp);
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);
#endif /* _CLIXON_XML_SORT_H */

View file

@ -128,9 +128,7 @@ text_modify(clicon_handle h,
int i;
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;
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
#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)
@ -583,11 +609,11 @@ xml_container_presence(cxobj *x,
*/
int
xmldb_put(clicon_handle h,
const char *db,
enum operation_type op,
cxobj *x1,
char *username,
cbuf *cbret)
const char *db,
enum operation_type op,
cxobj *x1,
char *username,
cbuf *cbret)
{
int retval = -1;
char *dbfile = NULL;

View file

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

View file

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

View file

@ -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,35 +1086,54 @@ 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){
if ((yc = xml_spec(x0c)) == NULL){
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x0c));
goto done;
/* 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;
}
/* Does x1 have a child matching x0c? */
if (match_base_child(x1, x0c, yc, &x1c) < 0)
goto done;
if (x1c == NULL){
else if (x1c == NULL){
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
goto done;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
else 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;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
/* 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 x0c and x1c are leafs w bodies, then they are changed */
if (yc->ys_keyword == Y_LEAF){
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;
}
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;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
goto done;
}
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,
x0vec, x0veclen,
x1vec, x1veclen,
changed_x0, changed_x1, changedlen)< 0)
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;
}
/* 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 */
x0c = xml_child_each(x0, x0c, CX_ELMNT);
x1c = xml_child_each(x1, x1c, CX_ELMNT);
}
ok:
retval = 0;
done:
return retval;
@ -1622,7 +1635,8 @@ xml_default(cxobj *xt,
cxobj *xc;
cxobj *xb;
char *str;
int added=0;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
@ -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
}
}
}
}
xml_sort(xt, NULL);
#ifndef USE_XML_INSERT
if (added)
xml_sort(xt, NULL);
#endif
retval = 0;
done:
return retval;
@ -1946,8 +1974,8 @@ api_path2xpath(yang_stmt *yspec,
* @param[out] xpathp Resulting xml tree
* @param[out] ypathp Yang spec matching xpathp
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called
* @retval -1 Fatal error, clicon_err called
* @retval 0 Invalid api_path or associated XML, clicon_err called
* @retval -1 Fatal error, clicon_err called
*
* @note both retval 0 and -1 set clicon_err, but the later is fatal
* @see api_path2xpath For api-path to xml xpath translation
@ -2156,8 +2184,8 @@ api_path2xml(char *api_path,
goto fail;
}
nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass,
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass,
xbotp, ybotp)) < 1)
goto done;
xml_yang_root(*xbotp, &xroot);

View file

@ -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
* @retval 0 If equal
* @retval <0 if x1 is less than x2
* @retval >0 if x1 is greater than x2
* @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
* @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,18 +221,18 @@ 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);
nr1 = xml_enumerate_get(x1);
nr2 = xml_enumerate_get(x2);
if (same){
nr1 = xml_enumerate_get(x1);
nr2 = xml_enumerate_get(x2);
}
if (y1==NULL && y2==NULL){
equal = nr1-nr2;
if (same)
equal = nr1-nr2;
goto done;
}
if (y1==NULL){
@ -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){
equal = nr1-nr2;
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
*/
static int
xml_insert2(cxobj *xp,
cxobj *xn,
yang_stmt *yn,
int yni,
int userorder,
int low,
int upper)
{
int retval = -1;
int mid;
int cmp;
cxobj *xc;
yang_stmt *yc;
int i;
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(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){
retval = mid;
goto done;
}
else if (cmp < 0)
return xml_insert2(xp, xn, yn, yni, userorder, low, mid);
else
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_pos(cxobj *x0,
char *name,
int yangi,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval,
int low,
int upper)
xml_insert(cxobj *xp,
cxobj *xi)
{
int mid;
cxobj *xc;
int retval = -1;
cxobj *xa;
int low = 0;
int upper;
yang_stmt *y;
int cmp;
int i;
int userorder= 0;
if (upper < low)
return low; /* not found */
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 (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 */
}
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);
else
return xml_insert_pos(x0, name, yangi, keyword,
keynr, keyvec, keyval, keycvec, mid+1, upper);
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;

View file

@ -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 (!yang_datanode(ys))
continue;
if (ys==y)
return 1;
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,

View file

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

View file

@ -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)
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)
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
# 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
new "start backend -s init -f $cfg -y $fyang"
start_backend -s init -f $cfg -y $fyang
fi
# start new backend
sudo clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
# Always as a start
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`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill 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
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
View 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

View file

@ -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>]]>]]>$"

View file

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