diff --git a/README.md b/README.md
index af2b3b3b..38385af8 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ support.
* [Runtime](#runtime)
* [Clixon project page](http://www.clicon.org)
* [Tests and CI](test/README.md)
+ * [Scaling: large lists](doc/large-lists.md)
* [Containers](docker/README.md)
* [Roadmap](doc/ROADMAP.md)
* [Reference manual](#reference)
diff --git a/doc/large-lists.md b/doc/large-lists.md
new file mode 100644
index 00000000..1b1b15a4
--- /dev/null
+++ b/doc/large-lists.md
@@ -0,0 +1,134 @@
+# Large lists in Clixon
+
+ * [Background](#background)
+ * [Overview](#overview)
+ * [Test descriptions]#test-descriptions)
+
+## Background
+
+Clixon is a configuration management tool. In this paper the case of
+a large number of "flat" list and leaf-list entries are investigated.
+There may be other scaling usecases, such as large configuratin
+"depth", large number of requesting clients, etc. However, these are
+not investigated here.
+
+## Overview
+The basic case is a large list, according to the following Yang specification:
+```
+ list y {
+ key "a";
+ leaf a {
+ type int32;
+ }
+ leaf b {
+ type string;
+ }
+ }
+```
+where `a` is a unique key and `b` is a payload, useful in replace operations.
+
+There is also a leaf-list as follows:
+```
+ leaf-list c {
+ type string;
+ }
+```
+
+XML lists with `N` elements are generated based on
+this configuration, eg for `N=10`:
+```
+ 00
+ 11
+ 22
+ 33
+ 44
+ 55
+ 66
+ 77
+ 88
+ 99
+```
+
+Requests are made using a random function, a request on the list above will on the form:
+```
+ curl -G http://localhost/restconf/data/y=(rnd%$N)
+```
+
+## Test descriptions
+
+### Limitations
+
+Test were not made using CLI interaction.
+
+### Setup
+
+The setup consisted of the following components running on the same machine:
+* A clixon backend daemon
+* A clixon restconf daemon
+* An nginx daemon daemon
+* A netconf client program
+* curl client
+* A bash terminal and test script [plot_perf.sh](../test/plot_perf.sh)
+* Gnuplot for generating plots
+
+### Config file
+The following Clixon config file was used:
+```
+
+ $cfg
+ $dir
+ /usr/local/share/clixon
+ scaling
+ /usr/local/var/example/example.sock
+ /usr/local/var/example/example.pidfile
+ false
+ $dir
+ false
+
+```
+where `$dir` and `$cfg`are local files. For more info see [plot_perf.sh].
+
+### Testcases
+
+All tests measure the "real" time of a command on a lightly loaded
+machine using the Linux command `time(1)`.
+
+The following tests were made (for each architecture and protocol):
+* Write `N` entries in one single operation. (With an empty datastore)
+* Read `N` entries in one single operation. (With a datastore of `N` entries)
+* Commit `N` entries (With a candidate of `N` entries and empty running)
+* Read 1 entry (In a datastore of `N` entries)
+* Write/Replace 1 entry (In a datastore of `N` entries)
+* Delete 1 entry (In a datastore of `N` entries)
+
+### Protocols
+
+The tests are made using:
+* Netconf[RFC6241] and
+* Restconf[RFC8040].
+Notably, CLI tests are for future study.
+
+### Architectures
+
+The tests were made on the following hardware, all running Ubuntu Linux:
+* [i686] dual Intel Core Duo processor (IBM Thinkpad X60), 3GB memory
+* arm 32-bit (Raspberry PI 3)
+* x86 64-bit (Intel NUC)
+
+### Operating systems
+
+On i686:
+```
+Linux version 4.4.0-143-generic (buildd@lgw01-amd64-037) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10) ) #169-Ubuntu SMP Thu Feb 7 07:56:51 UTC 2019
+```
+
+## Results
+
+## References
+
+[RFC6241](https://tools.ietf.org/html/rfc6241) "Network Configuration Protocol (NETCONF)"
+[RFC8040](https://tools.ietf.org/html/rfc8040) "RESTCONF Protocol"
+[i686](https://ark.intel.com/content/www/us/en/ark/products/27235/intel-core-duo-processor-t2400-2m-cache-1-83-ghz-667-mhz-fsb.html)
+[plot_perf.sh](../test/plot_perf.sh) Test script
+
+
diff --git a/docker/system/README.md b/docker/system/README.md
index 73378264..61febc54 100644
--- a/docker/system/README.md
+++ b/docker/system/README.md
@@ -54,3 +54,10 @@ To check status and then kill it:
```
You trigger the test scripts inside the container using `make test`.
+
+## Changing code
+
+If you want to edit clixon code so it runs in the container?
+You either
+(1) "persistent": make your changes in the actual clixon code and commit; make clean to remove the local clone; make test again
+(2) "volatile" edit the local clone; make test.
\ No newline at end of file
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 4a88311a..b19909a6 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -814,7 +814,7 @@ xml_cv_set(cxobj *x,
* name "name".
*
* @param[in] x_up Base XML object
- * @param[in] name shell wildcard pattern to match with node name
+ * @param[in] name Node name
*
* @retval xmlobj if found.
* @retval NULL if no such node found.
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 3154d18b..0f569048 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -2021,6 +2021,7 @@ api_path2xml_vec(char **vec,
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ymod;
+ yang_stmt *ykey;
char *namespace = NULL;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
@@ -2104,7 +2105,12 @@ api_path2xml_vec(char **vec,
/* Create keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
- if ((xn = xml_new(keyname, x, NULL)) == NULL)
+ if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
+ clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
+ yang_argument_get(y), keyname);
+ goto done;
+ }
+ if ((xn = xml_new(keyname, x, ykey)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new("body", xn, NULL)) == NULL)
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index 3d9c5bbf..7cfd6d70 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -89,7 +89,8 @@ xml_cv_cache(cxobj *x,
uint8_t fraction = 0;
char *body;
- body = xml_body(x);
+ if ((body = xml_body(x)) == NULL)
+ body="";
if ((cv = xml_cv(x)) != NULL)
goto ok;
if ((y = xml_spec(x)) == NULL)
@@ -191,13 +192,21 @@ xml_child_spec(cxobj *x,
* @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
+ * is used (see explanation below)
* @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
+ *
+ * There are distinct calls for this function:
+ * 1. For sorting in an existing list of XML children
+ * 2. For searching of an existing element in a list
+ * In the first case, there is a special case for "ordered-by-user", where
+ * if they have the same yang-spec, the existing order is used as tie-breaker.
+ * In other words, if order-by-system, or if the case (2) above, the existing
+ * order is ignored and the actual xml element contents is examined.
* @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)
* @note some error cases return as -1 (qsort cant handle errors)
*/
int
@@ -252,13 +261,15 @@ xml_cmp(cxobj *x1,
/* 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 the two elements are in the same list, and they are ordered-by user
+ * then do not look more into equivalence, use the enumeration in the
+ * existing list.
*/
- if (yang_config(y1)==0 ||
- yang_find(y1, Y_ORDERED_BY, "user") != NULL){
- if (same)
+ if (same &&
+ (yang_config(y1)==0 || yang_find(y1, Y_ORDERED_BY, "user") != NULL)){
equal = nr1-nr2;
- goto done; /* Ordered by user or state data : maintain existing order */
- }
+ goto done; /* Ordered by user or state data : maintain existing order */
+ }
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
if ((b1 = xml_body(x1)) == NULL)
@@ -289,8 +300,10 @@ xml_cmp(cxobj *x1,
else{
if (xml_cv_cache(x1b, &cv1) < 0) /* error case */
goto done;
+ assert(cv1);
if (xml_cv_cache(x2b, &cv2) < 0) /* error case */
goto done;
+ assert(cv2);
if ((equal = cv_cmp(cv1, cv2)) != 0)
goto done;
}
@@ -315,92 +328,6 @@ xml_cmp_qsort(const void* arg1,
return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1);
}
-/*! Compare xml object
- * @param[in] x XML node to compare with
- * @param[in] y The yang spec of x
- * @param[in] name Name to compare with x
- * @param[in] keyword Yang keyword (stmt type) to compare w x/y
- * @param[in] keynr Length of keyvec/keyval vector when applicable
- * @param[in] keyvec Array of of yang key identifiers
- * @param[in] keyval Array of of yang key values
- * @param[out] userorder If set, this yang order is user ordered, linear search
- * @retval 0 If equal (or userorder set)
- * @retval <0 if arg1 is less than arg2
- * @retval >0 if arg1 is greater than arg2
- * @see xml_cmp Similar, but for two objects
- * @note Does not care about y type of value as xml_cmp
- */
-static int
-xml_cmp1(cxobj *x,
- yang_stmt *y,
- char *name,
- enum rfc_6020 keyword,
- int keynr,
- char **keyvec,
- char **keyval,
- cg_var **keycvec,
- int *userorder)
-{
- char *b;
- cxobj *xb;
- int i;
- char *keyname;
- char *key;
- int match = 0;
- cg_var *cv;
-
- /* state data = userorder */
- if (userorder && yang_config(y)==0)
- *userorder=1;
- /* Check if same yang spec (order in yang stmt list) */
- switch (keyword){
- case Y_CONTAINER: /* Match with name */
- case Y_LEAF: /* Match with name */
- match = strcmp(name, xml_name(x));
- break;
- case Y_LEAF_LIST: /* Match with name and value */
- if (userorder && yang_find(y, Y_ORDERED_BY, "user") != NULL)
- *userorder=1;
- if ((b=xml_body(x)) == NULL)
- match = 1;
- else{
- if (keycvec[0]){
- if (xml_cv_cache(x, &cv) < 0) /* error case */
- goto done;
- match = cv_cmp(keycvec[0], cv);
- }
- else
- match = strcmp(keyval[0], b);
- }
- break;
- case Y_LIST: /* Match with array of key values */
- if (userorder && yang_find(y, Y_ORDERED_BY, "user") != NULL)
- *userorder=1;
- /* All must match */
- for (i=0; i=0; i--){ /* Then decrement */
- xc = xml_child_i(x0, i);
+ xc = xml_child_i(xp, i);
y = xml_spec(xc);
if (yangi!=yang_order(y))
break;
- if (xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, keycvec, NULL) == 0)
+ if (xml_cmp(xc, x1, 0) == 0)
return xc;
}
return NULL; /* Not found */
}
/*!
+ * @param[in] xp Parent xml node.
* @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
@@ -470,14 +394,10 @@ xml_search_userorder(cxobj *x0,
* @param[in] upper Lower bound of childvec search interval
*/
static cxobj *
-xml_search1(cxobj *x0,
- char *name,
+xml_search1(cxobj *xp,
+ cxobj *x1,
+ int userorder,
int yangi,
- enum rfc_6020 keyword,
- int keynr,
- char **keyvec,
- char **keyval,
- cg_var **keycvec,
int low,
int upper)
{
@@ -485,61 +405,65 @@ xml_search1(cxobj *x0,
int cmp;
cxobj *xc;
yang_stmt *y;
- int userorder= 0;
-
+
if (upper < low)
return NULL; /* not found */
mid = (low + upper) / 2;
- if (mid >= xml_child_nr(x0)) /* beyond range */
+ if (mid >= xml_child_nr(xp)) /* beyond range */
return NULL;
- xc = xml_child_i(x0, mid);
+ xc = xml_child_i(xp, mid);
if ((y = xml_spec(xc)) == NULL)
return NULL;
cmp = yangi-yang_order(y);
/* Here is right yang order == same yang? */
if (cmp == 0){
- cmp = xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, keycvec, &userorder);
- if (userorder && cmp) /* Look inside this yangi order */
- return xml_search_userorder(x0, y, name, yangi, mid, keyword, keynr, keyvec, keyval, keycvec);
+ if (userorder){
+ return xml_search_userorder(xp, x1, y, yangi, mid);
+ }
+ else /* Ordered by system */
+ cmp = xml_cmp(x1, xc, 0);
}
if (cmp == 0)
return xc;
else if (cmp < 0)
- return xml_search1(x0, name, yangi, keyword,
- keynr, keyvec, keyval, keycvec, low, mid-1);
+ return xml_search1(xp, x1, userorder, yangi, low, mid-1);
else
- return xml_search1(x0, name, yangi, keyword,
- keynr, keyvec, keyval, keycvec, mid+1, upper);
+ return xml_search1(xp, x1, userorder, yangi, mid+1, upper);
return NULL;
}
-/*! Find XML children using binary search
- * @param[in] yangi yang child order
+/*! Find XML child under xp matching x1 using binary search
+ * @param[in] xp Parent xml node.
+ * @param[in] yangi Yang child order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
*/
static cxobj *
-xml_search(cxobj *x0,
- char *name,
- int yangi,
- enum rfc_6020 keyword,
- int keynr,
- char **keyvec,
- char **keyval,
- cg_var **keycvec)
+xml_search(cxobj *xp,
+ cxobj *x1,
+ yang_stmt *yc)
{
- cxobj *xa;
- int low = 0;
- int upper = xml_child_nr(x0);
-
+ cxobj *xa;
+ int low = 0;
+ int upper = xml_child_nr(xp);
+ int userorder=0;
+ cxobj *xret = NULL;
+ int yangi;
+
/* Assume if there are any attributes, they are first in the list, mask
them by raising low to skip them */
for (low=0; lowys_keyword == Y_LIST || yc->ys_keyword == Y_LEAF_LIST)
+ userorder = (yang_find(yc, Y_ORDERED_BY, "user") != NULL);
+ yangi = yang_order(yc);
+ xret = xml_search1(xp, x1, userorder, yangi, low, upper);
+ return xret;
}
/*! Insert xn in xp:s sorted child list
@@ -604,12 +528,6 @@ xml_insert2(cxobj *xp,
*/
}
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;
@@ -733,15 +651,8 @@ match_base_child(cxobj *x0,
int retval = -1;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
- char *b;
cxobj *xb;
char *keyname;
- char keynr = 0;
- char **keyval = NULL;
- char **keyvec = NULL;
- cg_var **keycvec = NULL;
- int i;
- int yorder;
cxobj *x0c = NULL;
yang_stmt *y0c;
yang_stmt *y0p;
@@ -767,19 +678,10 @@ match_base_child(cxobj *x0,
case Y_LEAF: /* Equal regardless */
break;
case Y_LEAF_LIST: /* Match with name and value */
- keynr = 1;
- if ((keyval = calloc(keynr+1, sizeof(char*))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- if ((keyval[0] = xml_body(x1c)) == NULL)
+ if (xml_body(x1c) == NULL){ /* Treat as empty string */
+ // assert(0);
goto ok;
- if ((keycvec = calloc(keynr+1, sizeof(cg_var*))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
}
- if (xml_cv_cache(x1c, &keycvec[0]) < 0) /* error case */
- goto done;
break;
case Y_LIST: /* Match with key values */
cvk = yc->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
@@ -787,51 +689,22 @@ match_base_child(cxobj *x0,
* Then create two vectors one with names and one with values of x1c,
* ec: keyvec: [a,b,c] keyval: [1,2,3]
*/
- cvi = NULL; keynr = 0;
- while ((cvi = cvec_each(cvk, cvi)) != NULL)
- keynr++;
- if ((keyval = calloc(keynr+1, sizeof(char*))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- if ((keyvec = calloc(keynr+1, sizeof(char*))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- if ((keycvec = calloc(keynr+1, sizeof(char*))) == NULL){
- clicon_err(OE_UNIX, errno, "calloc");
- goto done;
- }
- cvi = NULL; i = 0;
+ cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
- keyvec[i] = keyname;
- if ((xb = xml_find(x1c, keyname)) == NULL)
+ // keyvec[i] = keyname;
+ if ((xb = xml_find(x1c, keyname)) == NULL){
goto ok;
- if ((b = xml_body(xb)) == NULL)
- goto ok;
- keyval[i] = b;
- if (xml_cv_cache(xb, &keycvec[i]) < 0) /* error case */
- goto done;
- i++;
+ }
}
- break;
default:
break;
}
/* Get match. */
- yorder = yang_order(yc);
- x0c = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval, keycvec);
+ x0c = xml_search(x0, x1c, yc);
ok:
*x0cp = x0c;
retval = 0;
- done:
- if (keyval)
- free(keyval);
- if (keyvec)
- free(keyvec);
- if (keycvec)
- free(keycvec);
return retval;
}
diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c
index 4b6c0937..4562d26c 100644
--- a/lib/src/clixon_yang_module.c
+++ b/lib/src/clixon_yang_module.c
@@ -159,7 +159,8 @@ yang_modules_revision(clicon_handle h)
}
/*! Actually build the yang modules state XML tree
-*/
+ * @see RFC7895
+ */
static int
yms_build(clicon_handle h,
yang_stmt *yspec,
@@ -197,8 +198,11 @@ yms_build(clicon_handle h,
cprintf(cb,"%s", ymod->ys_argument);
if ((ys = yang_find(ymod, Y_REVISION, NULL)) != NULL)
cprintf(cb,"%s", ys->ys_argument);
- else
+ else{
+ /* RFC7895 1 If no (such) revision statement exists, the module's or
+ submodule's revision is the zero-length string. */
cprintf(cb,"");
+ }
if ((ys = yang_find(ymod, Y_NAMESPACE, NULL)) != NULL)
cprintf(cb,"%s", ys->ys_argument);
else
diff --git a/test/lib.sh b/test/lib.sh
index 38a01380..61769b4c 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -212,7 +212,7 @@ expectfn(){
expect2=
fi
ret=$($cmd)
- r=$?
+ r=$?
# echo "cmd:\"$cmd\""
# echo "retval:\"$retval\""
# echo "expect:\"$expect\""
@@ -221,7 +221,7 @@ expectfn(){
if [ $r != $retval ]; then
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
echo -e "\e[0m:"
- return
+ exit -1
fi
# if [ $r != 0 ]; then
# return
diff --git a/test/plot_perf.sh b/test/plot_perf.sh
index 80c17c73..5cbc3cc7 100755
--- a/test/plot_perf.sh
+++ b/test/plot_perf.sh
@@ -1,6 +1,8 @@
#!/bin/bash
# Transactions per second for large lists read/write plotter using gnuplot
# What do I want to plot?
+# First: on i32, i64, arm32
+# PART 1: Basic load
# 1. How long to write 100K entries?
# - netconf / restconf
# - list / leaf-list
@@ -9,28 +11,36 @@
# - 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?
+#
+# PART 2: Load 100K entries. Commit.
+# 4. How many read operations per second?
# - netconf/ restconf
# - list / leaf-list
-# 5. 100K entries. How many write operations per second?
+# 5. How many write operations per second?
# - netconf / restconf
# - list / leaf-list
-# 6. 100K entries. How may delete operations per second?
+# 6. How may delete operations per second?
# - netconf / restconf
# - list / leaf-list
+# The script uses bash builtin "time" command which is somewhat difficult to
+# understand. See: https://linux.die.net/man/1/bash # pipelines
+# You essentially have to do: { time stuff; } 2>&1
+# See: https://stackoverflow.com/questions/26784870/parsing-the-output-of-bashs-time-builtin
# 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
+# op from step to reqs
+to=1000
+step=100
+reqs=100
# Global variables
APPNAME=example
cfg=$dir/plot-conf.xml
fyang=$dir/plot.yang
-fconfig=$dir/config
+fxml=$dir/data.xml
+fjson=$dir/data.json
# For memcheck
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
@@ -46,7 +56,7 @@ module scaling{
list y {
key "a";
leaf a {
- type string;
+ type uint32;
}
leaf b {
type string;
@@ -65,162 +75,257 @@ cat < $cfg
$dir/usr/local/share/clixonscaling
- /usr/local/var/$APPNAME/$APPNAME.sock
- /usr/local/var/example/$APPNAME.pidfile
+ /usr/local/var/example/example.sock
+ /usr/local/var/example/example.pidfilefalse$dirfalse
EOF
-# Run function
-# args:
-# where mode is one of:
-# readlist writelist restreadlist restwritelist
-runfn(){
- nr=$1 # Number of entries in DB
- reqs=$2
- operation=$3
-
-# echo "runfn nr=$nr reqs=$reqs mode=$mode"
+# Generate file with n entries
+# argument:
+genfile(){
+ if [ $2 = netconf ]; then
+ echo -n "replace" > $fxml
+ for (( i=0; i<$1; i++ )); do
+ echo -n "$i$i" >> $fxml
+ done
+ echo "]]>]]>" >> $fxml
+ else # restconf
+ echo -n '{"scaling:x":{"y":[' > $fjson
+ for (( i=0; i<$1; i++ )); do
+ if [ $i -ne 0 ]; then
+ echo -n ',' >> $fjson
+ fi
+ echo -n "{\"a\":$i,\"b\":\"$i\"}" >> $fjson
+ done
+ echo ']}}' >> $fjson
+ fi
+}
-# new "generate config with $nr list entries"
- echo -n "replace" > $fconfig
- for (( i=0; i<$nr; i++ )); do
- case $mode in
- readlist|writelist|restreadlist|restwritelist)
- echo -n "$i$i" >> $fconfig
- ;;
- writeleaflist)
- echo -n "$i" >> $fconfig
- ;;
- esac
- done
+# Run netconffunction
+# args:
+# where proto is one of:
+# netconf, restconf
+# where op is one of:
+# writeall readall commitall read write
+runnet(){
+ op=$1
+ n=$2 # Number of entries in DB
+ reqs=$3
- echo "]]>]]>" >> $fconfig
-
-# new "netconf write $nr entry to backend"
- expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>$"
-
- case $mode in
- readlist)
-# new "netconf GET list $reqs"
- time -p for (( i=0; i<$reqs; i++ )); do
- rnd=$(( ( RANDOM % $nr ) ))
- echo "]]>]]>"
-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 "$rnd$rnd]]>]]>"
-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/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 "$rnd]]>]]>"
-done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
+ echo -n "$n " >> $dir/$op-netconf-$reqs
+ case $op in
+ write)
+ if [ $reqs = 0 ]; then # Write all in one go
+ genfile $n netconf;
+ { time -p cat $fxml | $clixon_netconf -qf $cfg -y $fyang ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ else # reqs != 0
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $n ) ));
+ echo "$rnd$rnd]]>]]>";
+ done | $clixon_netconf -qf $cfg -y $fyang ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ fi
;;
+ read)
+ if [ $reqs = 0 ]; then # Read all in one go
+ { time -p echo "]]>]]>" | $clixon_netconf -qf $cfg -y $fyang > /dev/null ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ else # reqs != 0
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $nr ) ))
+ echo "$rnd$rnd]]>]]>"
+done | $clixon_netconf -qf $cfg -y $fyang; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ fi
+ ;;
+ delete)
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $nr ) ))
+ echo "$rnd$rnd]]>]]>"
+done | $clixon_netconf -qf $cfg -y $fyang; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ ;;
+ commit)
+ { time -p echo "]]>]]>" | $clixon_netconf -qf $cfg -y $fyang > /dev/null ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-netconf-$reqs
+ ;;
+ *)
+ err "Operation not supported" "$op"
+ exit
+ ;;
esac
-# new "discard test"
- expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$"
-
}
-# Step
-# args:
-stepfn(){
- i=$1
- reqs=$2
- mode=$3
-
->&2 echo "stepfn $mode: i=$i reqs=$reqs"
- echo -n "" > $fconfig
- 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
+# Run restconf function
+# args:
+# where proto is one of:
+# netconf, restconf
+# where op is one of:
+# writeall readall commitall read write
+runrest(){
+ op=$1
+ n=$2 # Number of entries in DB
+ reqs=$3
+
+ echo -n "$n " >> $dir/$op-restconf-$reqs
+ case $op in
+ write)
+ if [ $reqs = 0 ]; then # Write all in one go
+ genfile $n restconf
+ # restconf @- means from stdin
+ { time -p curl -sS -X PUT -d @$fjson http://localhost/restconf/data/scaling:x ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-restconf-$reqs
+ else # Small requests
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $n ) ));
+ curl -sS -X PUT http://localhost/restconf/data/scaling:x/y=$rnd -d "{\"scaling:y\":{\"a\":$rnd,\"b\":\"$rnd\"}}"
+ done ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-restconf-$reqs
+ #
+ fi
+ ;;
+ read)
+ if [ $reqs = 0 ]; then # Read all in one go
+ { time -p curl -sS -X GET http://localhost/restconf/data/scaling:x > /dev/null; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-restconf-$reqs
+ else # Small requests
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $n ) ));
+ curl -sS -X GET http://localhost/restconf/data/scaling:x/y=$rnd
+ done ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-restconf-$reqs
+ fi
+ ;;
+ delete)
+ { time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $n ) ));
+ curl -sS -X GET http://localhost/restconf/data/scaling:x/y=$rnd
+ done ; } 2>&1 | awk '/real/ {print $2}' >> $dir/$op-restconf-$reqs
+
+ ;;
+ *)
+ err "Operation not supported" "$op"
+ exit
+ ;;
+ esac
}
-# Run once
-#args:
-once(){
- # 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
+commit(){
+ # commit to running
+ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$"
+}
+
+reset(){
+ # delete all in candidate
+ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "none]]>]]>" '^]]>]]>$'
+ # commit to running
+ commit
+}
+
+# Load n entries into candidate
+# Args:
+load(){
+ # Generate file ($fxml)
+ genfile $1 netconf
+ # Write it to backend in one chunk
+ expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fxml" "^]]>]]>$"
+}
+
+# Run an operation, iterate from to in increment of
+# Each operation do times
+# args:
+# =0 means all in one go
+# means a priori loaded into datastore
+plot(){
+ op=$1
+ proto=$2
+ from=$3
+ step=$4
+ to=$5
+ reqs=$6
+ can=$7
+ run=$8
+
+ if [ $# -ne 8 ]; then
+ exit "plot should be called with 8 arguments, got $#"
fi
-
- 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
- stepfn $i $reqs readlist
- stepfn $i $reqs writelist
- stepfn $i $reqs restreadlist
- stepfn $i $reqs writeleaflist
- done
- 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
- stepfn $i $reqs readlist
- stepfn $i $reqs writelist
- stepfn $i $reqs restreadlist
- stepfn $i $reqs writeleaflist
- done
-
- 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"
+
+ # reset file
+ new "Create file $dir/$op-$proto-$reqs"
+ echo "" > $dir/$op-$proto-$reqs
+ for (( n=$from; n<=$to; n=$n+$step )); do
+ reset
+ if [ $can = n ]; then
+ load $n
+ if [ $run = n ]; then
+ commit
+ fi
fi
- # kill backend
- stop_backend -f $cfg
- fi
+ new "$op-$proto-$reqs $n"
+ if [ $proto = netconf ]; then
+ runnet $op $n $reqs
+ else
+ runrest $op $n $reqs
+ fi
+ done
+ echo # newline
}
-# step=200 reqs=500 max=2000
-once 200 500 1000
+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
+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
+
+for proto in netconf restconf; do
+ new "$proto write all entries to candidate (restconf:running)"
+ plot write $proto $step $step $to 0 0 0 # all candidate 0 running 0
+done
+
+for proto in netconf restconf; do
+ new "$proto read all entries from running"
+ plot read netconf $step $step $to 0 n n # start w full datastore
+done
+
+new "Netconf commit all entries from candidate to running"
+plot commit netconf $step $step $to 0 n 0 # candidate full running empty
+
+reqs=100
+for proto in netconf restconf; do
+ new "$proto read $reqs from full database"
+ plot read $proto $step $step $to $reqs n n
+
+ new "$proto Write $reqs to full database(replace / alter values)"
+ plot write $proto $step $step $to $reqs n n
+
+ new "$proto delete $reqs from full database(replace / alter values)"
+ plot delete $proto $step $step $to $reqs n n
+done
+
+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
+
+arch=$(arch)
gnuplot -persist <
+expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 "
"
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
index 543a9799..0ec36b11 100755
--- a/test/test_restconf2.sh
+++ b/test/test_restconf2.sh
@@ -95,7 +95,7 @@ expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"type":"regular"}}}
new "restconf POST initial tree"
expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 ""
-new "restconf GET datastore intial"
+new "restconf GET datastore initial"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}'
new "restconf GET interface subtree"