diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59f45c49..9e57114f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,15 @@
### Major changes:
### Minor changes:
+
* The following backward compatible options to configure have been obsoleted. If you havent already migrated this code you must do this now.
* Backend startup modes prior to 3.3.3. As enabled with `configure --with-startup-compat`. Configure option CLICON_USE_STARTUP_CONFIG is also obsoleted.
* Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed.
* Clixon XML C-lib prior to 3.4.0. As enabled with `configure --with-xml-compat`
* new configuration option: CLICON_RESTCONF_PRETTY
-* Changed RESTCONF GET to return object referenced. ie, GET /restconf/data/X returns X. Thanks Stephen Jones for getting this right.
+* Changed restconf GET to return object referenced. ie, GET /restconf/data/X returns X. Thanks Stephen Jones for getting this right.
+* Restconf: get well-known, top-level resource, yang library version, put whole datastore, check for different keys in put lists.
* Default configure file added by Matt Smith. Config file is selected in the following priority order:
* Provide -f option when starting a program.
diff --git a/apps/restconf/README.md b/apps/restconf/README.md
index a7952a9c..fdb1da37 100644
--- a/apps/restconf/README.md
+++ b/apps/restconf/README.md
@@ -2,16 +2,15 @@
### Features
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
-run with NGINX.
-The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE.
-and is based on draft-ietf-netconf-restconf-13.
-There is currently (2017) a [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040), many of those features are _not_ implemented,
-including:
+run with NGINX.
+The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
+The following featires are supported:
+- OPTIONS, HEAD, GET, POST, PUT, DELETE
+The following are not implemented
+- PATCH
- query parameters (section 4.9)
- notifications (sec 6)
-- GET /restconf/ (sec 3.3)
-- GET /restconf/yang-library-version (sec 3.3.3)
-- only rudimentary error reporting exists (sec 7)
+- schema resource
### Installation using Nginx
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index f400edf0..b386616b 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -73,9 +73,14 @@
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hDf:p:y:"
-/* Should be discovered via "/.well-known/host-meta"
- resource ([RFC6415]) */
-#define RESTCONF_API_ROOT "/restconf/"
+/* RESTCONF enables deployments to specify where the RESTCONF API is
+ located. The client discovers this by getting the "/.well-known/host-meta"
+ resource
+*/
+#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
+
+#define RESTCONF_API "restconf"
+#define RESTCONF_API_ROOT "/restconf"
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
@@ -117,6 +122,7 @@ api_data(clicon_handle h,
retval = api_data_delete(h, r, api_path, pi);
else
retval = notfound(r);
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
@@ -144,18 +150,118 @@ api_operations(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
- if (strcmp(request_method, "POST")==0)
+ if (strcmp(request_method, "GET")==0)
+ retval = api_operation_get(h, r, path, pcvec, pi, qvec, data);
+ else if (strcmp(request_method, "POST")==0)
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data);
else
retval = notfound(r);
return retval;
}
+/*! Retrieve the Top-Level API Resource
+ * @note Only returns null for operations and data,...
+ */
+static int
+api_root(clicon_handle h,
+ FCGX_Request *r)
+{
+ int retval = -1;
+ char *media_accept;
+ int use_xml = 0; /* By default use JSON */
+ cxobj *xt = NULL;
+ cbuf *cb = NULL;
+ int pretty;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
+ media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
+ if (strcmp(media_accept, "application/yang-data+xml")==0)
+ use_xml++;
+ FCGX_SetExitStatus(200, r->out); /* OK */
+ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
+ FCGX_FPrintF(r->out, "\r\n");
+ if (xml_parse_string("2016-06-21", NULL, &xt) < 0)
+ goto done;
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ if (use_xml){
+ if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
+ goto done;
+ }
+ else
+ if (xml2json_cbuf(cb, xt, pretty) < 0)
+ goto done;
+ FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):"");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (xt)
+ xml_free(xt);
+ return retval;
+}
+
+/*!
+ * See https://tools.ietf.org/html/rfc7895
+ */
+static int
+api_yang_library_version(clicon_handle h,
+ FCGX_Request *r)
+{
+ int retval = -1;
+ char *media_accept;
+ int use_xml = 0; /* By default use JSON */
+ cxobj *xt = NULL;
+ cbuf *cb = NULL;
+ int pretty;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
+ media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
+ if (strcmp(media_accept, "application/yang-data+xml")==0)
+ use_xml++;
+ FCGX_SetExitStatus(200, r->out); /* OK */
+ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
+ FCGX_FPrintF(r->out, "\r\n");
+ if (xml_parse_string("2016-06-21", NULL, &xt) < 0)
+ goto done;
+ if (xml_rootchild(xt, 0, &xt) < 0)
+ goto done;
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "cbuf_new");
+ goto done;
+ }
+ if (use_xml){
+ if (clicon_xml2cbuf(cb, xt, 0, pretty) < 0)
+ goto done;
+ }
+ else{
+ if (xml2json_cbuf(cb, xt, pretty) < 0)
+ goto done;
+ }
+ clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb));
+ FCGX_FPrintF(r->out, "%s\r\n", cb?cbuf_get(cb):"");
+ FCGX_FPrintF(r->out, "\r\n\r\n");
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (xt)
+ xml_free(xt);
+ return retval;
+}
+
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
static int
-request_process(clicon_handle h,
+api_restconf(clicon_handle h,
FCGX_Request *r)
{
int retval = -1;
@@ -176,7 +282,28 @@ request_process(clicon_handle h,
query = FCGX_GetParam("QUERY_STRING", r->envp);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
-
+ /* Sanity check of path. Should be /restconf/ */
+ if (pn < 2){
+ retval = notfound(r);
+ goto done;
+ }
+ if (strlen(pvec[0]) != 0){
+ retval = notfound(r);
+ goto done;
+ }
+ if (strcmp(pvec[1], RESTCONF_API)){
+ retval = notfound(r);
+ goto done;
+ }
+ if (pn == 2){
+ retval = api_root(h, r);
+ goto done;
+ }
+ if ((method = pvec[2]) == NULL){
+ retval = notfound(r);
+ goto done;
+ }
+ clicon_debug(1, "method=%s", method);
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
@@ -188,10 +315,7 @@ request_process(clicon_handle h,
clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
- if ((method = pvec[2]) == NULL){
- retval = notfound(r);
- goto done;
- }
+
retval = 0;
test(r, 1);
/* If present, check credentials */
@@ -202,8 +326,9 @@ request_process(clicon_handle h,
if (auth == 0)
goto done;
clicon_debug(1, "%s credentials ok 2", __FUNCTION__);
-
- if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
+ if (strcmp(method, "yang-library-version")==0)
+ retval = api_yang_library_version(h, r);
+ else if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
retval = api_data(h, r, path, pcvec, 2, qvec, data);
else if (strcmp(method, "operations") == 0) /* rpc */
retval = api_operations(h, r, path, pcvec, 2, qvec, data);
@@ -226,6 +351,24 @@ request_process(clicon_handle h,
return retval;
}
+/*! Process a FastCGI request
+ * @param[in] r Fastcgi request handle
+ */
+static int
+api_well_known(clicon_handle h,
+ FCGX_Request *r)
+{
+ clicon_debug(1, "%s", __FUNCTION__);
+ FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+ FCGX_SetExitStatus(200, r->out); /* OK */
+ FCGX_FPrintF(r->out, "\r\n");
+ FCGX_FPrintF(r->out, " \r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+
+ return 0;
+}
+
static int
restconf_terminate(clicon_handle h)
{
@@ -384,13 +527,17 @@ main(int argc,
}
clicon_debug(1, "------------");
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
- if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
- strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
- request_process(h, r); /* This is the function */
+ clicon_debug(1, "path:%s", path);
+ if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
+ api_restconf(h, r); /* This is the function */
+ else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
+ api_well_known(h, r); /* This is the function */
+ }
else{
- clicon_debug(1, "top-level not found");
+ clicon_debug(1, "top-level %s not found", path);
notfound(r);
}
+
}
else
clicon_debug(1, "NULL URI");
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 48313a9f..859cf210 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -379,7 +379,7 @@ api_data_get(clicon_handle h,
return api_data_get2(h, r, pcvec, pi, qvec, 0);
}
-/*! REST POST method
+/*! Generic REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
@@ -387,7 +387,7 @@ api_data_get(clicon_handle h,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
- * @note We map post to edit-config create.
+ * @note restconf POST is mapped to edit-config create.
POST:
target resource type is datastore --> create a top-level resource
target resource type is data resource --> create child resource
@@ -414,12 +414,12 @@ api_data_post(clicon_handle h,
cvec *qvec,
char *data)
{
- enum operation_type op = OP_CREATE;
int retval = -1;
+ enum operation_type op = OP_CREATE;
int i;
cxobj *xdata = NULL;
- cxobj *xtop = NULL; /* xpath root */
cbuf *cbx = NULL;
+ cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
cxobj *x;
yang_node *y = NULL;
@@ -444,8 +444,8 @@ api_data_post(clicon_handle h,
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
+ /* Translate api_path to xtop/xbot */
xbot = xtop;
- /* xbot is resulting xml tree on exit */
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
goto done;
/* Parse input data as json or xml into xml */
@@ -456,29 +456,35 @@ api_data_post(clicon_handle h,
}
}
else if (json_parse_str(data, &xdata) < 0){
- badrequest(r);
- goto ok;
+ badrequest(r);
+ goto ok;
}
- /* Add xdata to xbot */
- x = NULL;
- while ((x = xml_child_each(xdata, x, CX_ELMNT)) != NULL) {
- if ((xa = xml_new("operation", x, NULL)) == NULL)
- goto done;
- xml_type_set(xa, CX_ATTR);
- if (xml_value_set(xa, xml_operation2str(op)) < 0)
- goto done;
- if (xml_addsub(xbot, x) < 0)
- goto done;
+ /* The message-body MUST contain exactly one instance of the
+ * expected data resource.
+ */
+ if (xml_child_nr(xdata) != 1){
+ badrequest(r);
+ goto ok;
}
+ x = xml_child_i(xdata,0);
+ /* Add operation (create/replace) as attribute */
+ if ((xa = xml_new("operation", x, NULL)) == NULL)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if (xml_value_set(xa, xml_operation2str(op)) < 0)
+ goto done;
+ /* Replace xbot with x, ie bottom of api-path with data */
+ if (xml_addsub(xbot, x) < 0)
+ goto done;
+ /* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
- clicon_debug(1, "%s xml: %s",__FUNCTION__, cbuf_get(cbx));
+ clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_edit_config(h, "candidate",
OP_NONE,
cbuf_get(cbx)) < 0){
- // notfound(r); /* XXX */
conflict(r);
goto ok;
}
@@ -492,7 +498,6 @@ api_data_post(clicon_handle h,
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
- // XXX api_path can be null FCGX_FPrintF(r->out, "Location: %s\r\n", api_path);
FCGX_FPrintF(r->out, "\r\n");
ok:
retval = 0;
@@ -505,6 +510,58 @@ api_data_post(clicon_handle h,
if (cbx)
cbuf_free(cbx);
return retval;
+} /* api_data_post */
+
+
+/*! Check matching keys
+ *
+ * @param[in] y Yang statement, should be list or leaf-list
+ * @param[in] xdata XML data tree
+ * @param[in] xapipath XML api-path tree
+ * @retval 0 Yes, keys match
+ * @retval -1 No keys do not match
+ * If the target resource represents a YANG leaf-list, then the PUT
+ * method MUST NOT change the value of the leaf-list instance.
+ *
+ * If the target resource represents a YANG list instance, then the key
+ * leaf values, in message-body representation, MUST be the same as the
+ * key leaf values in the request URI. The PUT method MUST NOT be used
+ * to change the key leaf values for a data resource instance.
+ */
+static int
+match_list_keys(yang_stmt *y,
+ cxobj *xdata,
+ cxobj *xapipath)
+{
+ int retval = -1;
+ cvec *cvk = NULL; /* vector of index keys */
+ cg_var *cvi;
+ char *keyname;
+ cxobj *xkeya; /* xml key object in api-path */
+ cxobj *xkeyd; /* xml key object in data */
+ char *keya;
+ char *keyd;
+
+ if (y->ys_keyword != Y_LIST &&y->ys_keyword != Y_LEAF_LIST)
+ return -1;
+ cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
+ cvi = NULL;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ if ((xkeya = xml_find(xapipath, keyname)) == NULL)
+ goto done; /* No key in api-path */
+
+ keya = xml_body(xkeya);
+ if ((xkeyd = xml_find(xdata, keyname)) == NULL)
+ goto done; /* No key in data */
+ keyd = xml_body(xkeyd);
+ if (strcmp(keya, keyd) != 0)
+ goto done; /* keys dont match */
+ }
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ return retval;
}
/*! Generic REST PUT method
@@ -515,6 +572,7 @@ api_data_post(clicon_handle h,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
+ * @note restconf PUT is mapped to edit-config replace.
* @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
@@ -528,7 +586,7 @@ api_data_post(clicon_handle h,
int
api_data_put(clicon_handle h,
FCGX_Request *r,
- char *api_path,
+ char *api_path0,
cvec *pcvec,
int pi,
cvec *qvec,
@@ -539,19 +597,19 @@ api_data_put(clicon_handle h,
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
- cxobj *x;
+ cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
- cxobj *xtop = NULL;
- cxobj *xp;
+ cxobj *xparent;
+ cxobj *x;
yang_node *y = NULL;
yang_spec *yspec;
cxobj *xa;
char *media_content_type;
int parse_xml = 0; /* By default expect and parse JSON */
+ char *api_path;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
- __FUNCTION__,
- api_path, data);
+ __FUNCTION__, api_path0, data);
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (media_content_type &&
strcmp(media_content_type, "application/yang-data+xml")==0)
@@ -560,11 +618,13 @@ api_data_put(clicon_handle h,
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
+ api_path=api_path0;
for (i=0; iyn_keyword == Y_LIST ||y->yn_keyword == Y_LEAF_LIST)){
+ if (match_list_keys((yang_stmt*)y, x, xbot) < 0){
+ badrequest(r);
+ goto ok;
+ }
+ }
+ xparent = xml_parent(xbot);
+ xml_purge(xbot);
+ if (xml_addsub(xparent, x) < 0)
+ goto done;
+ }
+#endif
+ /* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
@@ -628,8 +714,7 @@ api_data_put(clicon_handle h,
if (cbx)
cbuf_free(cbx);
return retval;
-
-}
+} /* api_data_put */
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
@@ -724,6 +809,20 @@ api_data_delete(clicon_handle h,
return retval;
}
+/*! NYI
+ */
+int
+api_operation_get(clicon_handle h,
+ FCGX_Request *r,
+ char *path,
+ cvec *pcvec,
+ int pi,
+ cvec *qvec,
+ char *data)
+{
+ return 0;
+}
+
/*! REST operation POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h
index 89ece997..0aef4428 100644
--- a/apps/restconf/restconf_methods.h
+++ b/apps/restconf/restconf_methods.h
@@ -60,6 +60,10 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *qvec, char *data);
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi);
+int api_operation_get(clicon_handle h, FCGX_Request *r,
+ char *path,
+ cvec *pcvec, int pi, cvec *qvec, char *data);
+
int api_operation_post(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data);
diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
index 9eae988e..739e2256 100644
--- a/datastore/text/clixon_xmldb_text.c
+++ b/datastore/text/clixon_xmldb_text.c
@@ -748,14 +748,19 @@ text_modify_top(cxobj *x0,
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc; /* yang child */
+ char *opstr;
/* Assure top-levels are 'config' */
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)
+ if (xml_operation(opstr, &op) < 0)
+ goto done;
/* Special case if x1 is empty, top-level only */
- if (!xml_child_nr(x1)){ /* base tree not empty */
- if (xml_child_nr(x0))
+ if (!xml_child_nr(x1)){
+ if (xml_child_nr(x0)) /* base tree not empty */
switch(op){
case OP_DELETE:
case OP_REMOVE:
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index bb4d6ade..bd7c1dbd 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -1654,9 +1654,9 @@ api_path2xml(char *api_path,
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
- clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
- goto done;
- }
+ clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
+ goto done;
+ }
nvec--; /* NULL-terminated */
if (api_path2xml_vec(vec+1, nvec,
xpath, (yang_node*)yspec, schemanode,
diff --git a/test/plot_perf.sh b/test/plot_perf.sh
index 04b48f87..db73f7cc 100755
--- a/test/plot_perf.sh
+++ b/test/plot_perf.sh
@@ -2,7 +2,7 @@
# Transactions per second for large lists read/write plotter using gnuplot
#
. ./lib.sh
-max=1000 # Nr of db entries
+max=200 # Nr of db entries
step=100
reqs=1000
cfg=$dir/scaling-conf.xml
@@ -48,27 +48,55 @@ EOF
run(){
nr=$1 # Number of entries in DB
reqs=$2
- write=$3
+ mode=$3
- echo -n "" > $fconfig
+ echo -n "replace" > $fconfig
for (( i=0; i<$nr; i++ )); do
+ echo -n "$i" >> $fconfig
echo -n "$i$i" >> $fconfig
done
echo "]]>]]>" >> $fconfig
expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>$"
- if $write; then
- time -p for (( i=0; i<$reqs; i++ )); do
- rnd=$(( ( RANDOM % $nr ) ))
- echo "$rnd$rnd]]>]]>"
-done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
- else # read
+ case $mode in
+ readlist)
time -p for (( i=0; i<$reqs; i++ )); do
rnd=$(( ( RANDOM % $nr ) ))
echo "]]>]]>"
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
- fi
+ ;;
+ writelist)
+ time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $nr ) ))
+ echo "$rnd$rnd]]>]]>"
+done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
+ ;;
+ readleaflist)
+ time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $nr ) ))
+ echo "]]>]]>"
+done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
+ ;;
+ writeleaflist)
+ time -p for (( i=0; i<$reqs; i++ )); do
+ rnd=$(( ( RANDOM % $nr ) ))
+ echo "$rnd]]>]]>"
+done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
+ ;;
+ esac
+ expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$"
+}
+
+step(){
+ i=$1
+ mode=$2
+ echo -n "" > $fconfig
+ t=$(TEST=%e run $i $reqs $mode $ 2>&1 | awk '/real/ {print $2}')
+ # t is time in secs of $reqs -> transactions per second. $reqs
+ p=$(echo "$reqs/$t" | bc -lq)
+ # p is transactions per second.
+ echo "$i $p" >> $dir/$mode
}
once()(
@@ -84,18 +112,19 @@ once()(
err
fi
+ # Always as a start
+ for (( i=10; i<=$step; i=i+10 )); do
+ step $i readlist
+ step $i writelist
+ step $i readleaflist
+ step $i writeleaflist
+ done
# Actual steps
for (( i=$step; i<=$max; i=i+$step )); do
- t=$(TEST=%e run $i $reqs true $ 2>&1 | awk '/real/ {print $2}')
- # t is time in secs of $reqs -> transactions per second. $reqs
- p=$(echo "$reqs/$t" | bc -lq)
- # p is transactions per second.
- echo "$i $p" >> $dir/write
- t=$(TEST=%e run $i $reqs false $ 2>&1 | awk '/real/ {print $2}')
- # t is time in secs of $reqs -> transactions per second. $reqs
- p=$(echo "$reqs/$t" | bc -lq)
- # p is transactions per second.
- echo "$i $p" >> $dir/read
+ step $i readlist
+ step $i readleaflist
+ step $i writelist
+ step $i writeleaflist
done
# Check if still alive
@@ -118,7 +147,7 @@ set title "Clixon transactions per second r/w large lists" font ",14" textcolor
set xlabel "entries"
set ylabel "transactions per second"
set terminal wxt enhanced title "CLixon transactions " persist raise
-plot "$dir/read" with linespoints title "read", "$dir/write" with linespoints title "write"
+plot "$dir/readlist" with linespoints title "read list", "$dir/writelist" with linespoints title "write list", "$dir/readleaflist" with linespoints title "read leaf-list", "$dir/writeleaflist" with linespoints title "write leaf-list"
EOF
rm -rf $dir
diff --git a/test/test_restconf.sh b/test/test_restconf.sh
index 1593e633..8cc700d6 100755
--- a/test/test_restconf.sh
+++ b/test/test_restconf.sh
@@ -88,15 +88,15 @@ expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
new "restconf root discovery"
expectfn "curl -sS -X GET http://localhost/.well-known/host-meta" ""
+new "restconf get restconf json"
+expectfn "curl -sSG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}'
+
+new "restconf get restconf/yang-library-version json"
+expectfn "curl -sSG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}'
+
new "restconf empty rpc"
expectfn 'curl -sS -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}'
-#new "restconf get restconf json XXX"
-#expectfn "curl -sSG http://localhost/restconf" "{\"restconf\" : $state }"
-
-#new "restconf get restconf/yang-library-version json XXX"
-#expectfn "curl -sSG http://localhost/restconf/yang-library-version" "{\"restconf\" : $state }"
-
new "restconf get empty config + state json"
expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}"
diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh
new file mode 100755
index 00000000..1419f41b
--- /dev/null
+++ b/test/test_restconf2.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+# Restconf basic functionality
+# Assume http server setup, such as nginx described in apps/restconf/README.md
+
+# include err() and new() functions and creates $dir
+. ./lib.sh
+cfg=$dir/conf.xml
+fyang=$dir/restconf.yang
+
+# example
+cat < $cfg
+
+ $cfg
+ /usr/local/var
+ $fyang
+
+ false
+ /usr/local/var/routing/routing.sock
+ /usr/local/var/routing/routing.pidfile
+ 1
+ /usr/local/var/routing
+ /usr/local/lib/xmldb/text.so
+
+EOF
+
+cat < $fyang
+module example{
+ container interfaces-config{
+ list interface{
+ key name;
+ leaf name{
+ type string;
+ }
+ leaf type{
+ type string;
+ }
+ leaf description{
+ type string;
+ }
+ leaf netgate-if-type{
+ type string;
+ }
+ leaf enabled{
+ type boolean;
+ }
+ }
+ }
+}
+EOF
+
+# kill old backend (if any)
+new "kill old backend"
+sudo clixon_backend -zf $cfg
+if [ $? -ne 0 ]; then
+ err
+fi
+new "start backend -s init -f $cfg -y $fyang"
+sudo clixon_backend -s init -f $cfg -y $fyang
+if [ $? -ne 0 ]; then
+ err
+fi
+
+new "kill old restconf daemon"
+sudo pkill -u www-data clixon_restconf
+
+new "start restconf daemon"
+sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df $cfg
+
+sleep 1
+
+new "restconf tests"
+
+new "restconf PUT change key error"
+#expectfn 'curl -s -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' "fail"
+#exit
+
+new "restconf POST initial tree"
+expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","netgate-if-type":"regular"}}} http://localhost/restconf/data' ""
+
+new "restconf GET datastore"
+expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","netgate-if-type": "regular"}}}}'
+
+new "restconf GET interface"
+expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0" '{"interface": {"name": "local0","netgate-if-type": "regular"}}'
+
+new "restconf GET if-type"
+expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0/netgate-if-type" '{"netgate-if-type": "regular"}'
+
+new "restconf POST interface"
+expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' ""
+
+new "restconf POST again"
+expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' "Data resource already exis"
+
+new "restconf POST from top"
+expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists"
+
+new "restconf DELETE"
+expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces-config' ""
+
+new "restconf GET null datastore"
+expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}'
+
+new "restconf POST initial tree"
+expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","netgate-if-type":"regular"}}} http://localhost/restconf/data' ""
+
+new "restconf PUT initial datastore"
+
+expectfn 'curl -s -X PUT -d {"data":{"interfaces-config":{"interface":{"name":"local0","netgate-if-type":"regular"}}}} http://localhost/restconf/data' ""
+
+new "restconf GET datastore"
+expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","netgate-if-type": "regular"}}}}'
+
+new "restconf PUT change interface"
+expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/interfaces-config/interface=local0' ""
+
+new "restconf GET datastore"
+expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": {"name": "local0","type": "atm0"}}}}'
+
+new "restconf PUT add interface"
+expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' ""
+
+new "restconf PUT change key error"
+expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' "Bad request"
+
+new "Kill restconf daemon"
+sudo pkill -u www-data clixon_restconf
+
+new "Kill backend"
+# 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"
+fi
+
+rm -rf $dir
diff --git a/yang/ietf-yang-library@2016-06-21.yang b/yang/ietf-yang-library@2016-06-21.yang
new file mode 100644
index 00000000..1e897180
--- /dev/null
+++ b/yang/ietf-yang-library@2016-06-21.yang
@@ -0,0 +1,242 @@
+module ietf-yang-library {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-yang-library";
+ prefix "yanglib";
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ietf-inet-types {
+ prefix inet;
+ }
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "WG Web:
+ WG List:
+
+ WG Chair: Mehmet Ersue
+
+
+ WG Chair: Mahesh Jethanandani
+
+
+ Editor: Andy Bierman
+
+
+ Editor: Martin Bjorklund
+
+
+ Editor: Kent Watsen
+ ";
+
+ description
+ "This module contains monitoring information about the YANG
+ modules and submodules that are used within a YANG-based
+ server.
+
+ Copyright (c) 2016 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 7895; see
+ the RFC itself for full legal notices.";
+
+ revision 2016-06-21 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7895: YANG Module Library.";
+ }
+
+ /*
+ * Typedefs
+ */
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.";
+ }
+
+ /*
+ * Groupings
+ */
+
+ grouping module-list {
+ description
+ "The module data structure is represented as a grouping
+ so it can be reused in configuration or another monitoring
+ data structure.";
+
+ grouping common-leafs {
+ description
+ "Common parameters for YANG modules and submodules.";
+
+ leaf name {
+ type yang:yang-identifier;
+ description
+ "The YANG module or submodule name.";
+ }
+ leaf revision {
+ type union {
+ type revision-identifier;
+ type string { length 0; }
+ }
+ description
+ "The YANG module or submodule revision date.
+ A zero-length string is used if no revision statement
+ is present in the YANG module or submodule.";
+ }
+ }
+
+ grouping schema-leaf {
+ description
+ "Common schema leaf parameter for modules and submodules.";
+ leaf schema {
+ type inet:uri;
+ description
+ "Contains a URL that represents the YANG schema
+ resource for this module or submodule.
+
+ This leaf will only be present if there is a URL
+ available for retrieval of the schema for this entry.";
+ }
+ }
+
+ list module {
+ key "name revision";
+ description
+ "Each entry represents one revision of one module
+ currently supported by the server.";
+
+ uses common-leafs;
+ uses schema-leaf;
+
+ leaf namespace {
+ type inet:uri;
+ mandatory true;
+ description
+ "The XML namespace identifier for this module.";
+ }
+ leaf-list feature {
+ type yang:yang-identifier;
+ description
+ "List of YANG feature names from this module that are
+ supported by the server, regardless of whether they are
+ defined in the module or any included submodule.";
+ }
+ list deviation {
+ key "name revision";
+ description
+ "List of YANG deviation module names and revisions
+ used by this server to modify the conformance of
+ the module associated with this entry. Note that
+ the same module can be used for deviations for
+ multiple modules, so the same entry MAY appear
+ within multiple 'module' entries.
+
+ The deviation module MUST be present in the 'module'
+ list, with the same name and revision values.
+ The 'conformance-type' value will be 'implement' for
+ the deviation module.";
+ uses common-leafs;
+ }
+ leaf conformance-type {
+ type enumeration {
+ enum implement {
+ description
+ "Indicates that the server implements one or more
+ protocol-accessible objects defined in the YANG module
+ identified in this entry. This includes deviation
+ statements defined in the module.
+
+ For YANG version 1.1 modules, there is at most one
+ module entry with conformance type 'implement' for a
+ particular module name, since YANG 1.1 requires that,
+ at most, one revision of a module is implemented.
+
+ For YANG version 1 modules, there SHOULD NOT be more
+ than one module entry for a particular module name.";
+ }
+ enum import {
+ description
+ "Indicates that the server imports reusable definitions
+ from the specified revision of the module but does
+ not implement any protocol-accessible objects from
+ this revision.
+
+ Multiple module entries for the same module name MAY
+ exist. This can occur if multiple modules import the
+ same module but specify different revision dates in
+ the import statements.";
+ }
+ }
+ mandatory true;
+ description
+ "Indicates the type of conformance the server is claiming
+ for the YANG module identified by this entry.";
+ }
+ list submodule {
+ key "name revision";
+ description
+ "Each entry represents one submodule within the
+ parent module.";
+ uses common-leafs;
+ uses schema-leaf;
+ }
+ }
+ }
+
+
+
+ /*
+ * Operational state data nodes
+ */
+
+ container modules-state {
+ config false;
+ description
+ "Contains YANG module monitoring information.";
+
+ leaf module-set-id {
+ type string;
+ mandatory true;
+ description
+ "Contains a server-specific identifier representing
+ the current set of modules and submodules. The
+ server MUST change the value of this leaf if the
+ information represented by the 'module' list instances
+ has changed.";
+ }
+
+ uses module-list;
+ }
+
+ /*
+ * Notifications
+ */
+ notification yang-library-change {
+ description
+ "Generated when the set of modules and submodules supported
+ by the server has changed.";
+ leaf module-set-id {
+ type leafref {
+ path "/yanglib:modules-state/yanglib:module-set-id";
+ }
+ mandatory true;
+ description
+ "Contains the module-set-id value representing the
+ set of modules and submodules supported at the server at
+ the time the notification is generated.";
+ }
+ }
+}
\ No newline at end of file