Restconf: get well-known, top-level resource, yang library version, put whole datastore,
check for different keys in put lists.
This commit is contained in:
parent
1ee3f7e67e
commit
26667b2c2f
11 changed files with 759 additions and 91 deletions
|
|
@ -5,13 +5,15 @@
|
||||||
### Major changes:
|
### Major changes:
|
||||||
### Minor 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.
|
* 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.
|
* 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.
|
* 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`
|
* Clixon XML C-lib prior to 3.4.0. As enabled with `configure --with-xml-compat`
|
||||||
|
|
||||||
* new configuration option: CLICON_RESTCONF_PRETTY
|
* 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:
|
* Default configure file added by Matt Smith. Config file is selected in the following priority order:
|
||||||
* Provide -f option when starting a program.
|
* Provide -f option when starting a program.
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,15 @@
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
|
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
|
||||||
run with NGINX.
|
run with NGINX.
|
||||||
The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE.
|
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
|
||||||
and is based on draft-ietf-netconf-restconf-13.
|
The following featires are supported:
|
||||||
There is currently (2017) a [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040), many of those features are _not_ implemented,
|
- OPTIONS, HEAD, GET, POST, PUT, DELETE
|
||||||
including:
|
The following are not implemented
|
||||||
|
- PATCH
|
||||||
- query parameters (section 4.9)
|
- query parameters (section 4.9)
|
||||||
- notifications (sec 6)
|
- notifications (sec 6)
|
||||||
- GET /restconf/ (sec 3.3)
|
- schema resource
|
||||||
- GET /restconf/yang-library-version (sec 3.3.3)
|
|
||||||
- only rudimentary error reporting exists (sec 7)
|
|
||||||
|
|
||||||
### Installation using Nginx
|
### Installation using Nginx
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,14 @@
|
||||||
/* Command line options to be passed to getopt(3) */
|
/* Command line options to be passed to getopt(3) */
|
||||||
#define RESTCONF_OPTS "hDf:p:y:"
|
#define RESTCONF_OPTS "hDf:p:y:"
|
||||||
|
|
||||||
/* Should be discovered via "/.well-known/host-meta"
|
/* RESTCONF enables deployments to specify where the RESTCONF API is
|
||||||
resource ([RFC6415]) */
|
located. The client discovers this by getting the "/.well-known/host-meta"
|
||||||
#define RESTCONF_API_ROOT "/restconf/"
|
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
|
/*! Generic REST method, GET, PUT, DELETE, etc
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
|
|
@ -117,6 +122,7 @@ api_data(clicon_handle h,
|
||||||
retval = api_data_delete(h, r, api_path, pi);
|
retval = api_data_delete(h, r, api_path, pi);
|
||||||
else
|
else
|
||||||
retval = notfound(r);
|
retval = notfound(r);
|
||||||
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,18 +150,118 @@ api_operations(clicon_handle h,
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
|
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
|
||||||
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
|
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);
|
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data);
|
||||||
else
|
else
|
||||||
retval = notfound(r);
|
retval = notfound(r);
|
||||||
return retval;
|
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("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", 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("<yang-library-version>2016-06-21</yang-library-version>", 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
|
/*! Process a FastCGI request
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
request_process(clicon_handle h,
|
api_restconf(clicon_handle h,
|
||||||
FCGX_Request *r)
|
FCGX_Request *r)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -176,7 +282,28 @@ request_process(clicon_handle h,
|
||||||
query = FCGX_GetParam("QUERY_STRING", r->envp);
|
query = FCGX_GetParam("QUERY_STRING", r->envp);
|
||||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||||
goto done;
|
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)
|
if (str2cvec(query, '&', '=', &qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
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);
|
clicon_debug(1, "DATA=%s", data);
|
||||||
if (str2cvec(data, '&', '=', &dvec) < 0)
|
if (str2cvec(data, '&', '=', &dvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((method = pvec[2]) == NULL){
|
|
||||||
retval = notfound(r);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
test(r, 1);
|
test(r, 1);
|
||||||
/* If present, check credentials */
|
/* If present, check credentials */
|
||||||
|
|
@ -202,8 +326,9 @@ request_process(clicon_handle h,
|
||||||
if (auth == 0)
|
if (auth == 0)
|
||||||
goto done;
|
goto done;
|
||||||
clicon_debug(1, "%s credentials ok 2", __FUNCTION__);
|
clicon_debug(1, "%s credentials ok 2", __FUNCTION__);
|
||||||
|
if (strcmp(method, "yang-library-version")==0)
|
||||||
if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
|
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);
|
retval = api_data(h, r, path, pcvec, 2, qvec, data);
|
||||||
else if (strcmp(method, "operations") == 0) /* rpc */
|
else if (strcmp(method, "operations") == 0) /* rpc */
|
||||||
retval = api_operations(h, r, path, pcvec, 2, qvec, data);
|
retval = api_operations(h, r, path, pcvec, 2, qvec, data);
|
||||||
|
|
@ -226,6 +351,24 @@ request_process(clicon_handle h,
|
||||||
return retval;
|
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, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\r\n");
|
||||||
|
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\r\n");
|
||||||
|
FCGX_FPrintF(r->out, "</XRD>\r\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
restconf_terminate(clicon_handle h)
|
restconf_terminate(clicon_handle h)
|
||||||
{
|
{
|
||||||
|
|
@ -384,13 +527,17 @@ main(int argc,
|
||||||
}
|
}
|
||||||
clicon_debug(1, "------------");
|
clicon_debug(1, "------------");
|
||||||
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
|
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
|
||||||
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
|
clicon_debug(1, "path:%s", path);
|
||||||
strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
|
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
|
||||||
request_process(h, r); /* This is the function */
|
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{
|
else{
|
||||||
clicon_debug(1, "top-level not found");
|
clicon_debug(1, "top-level %s not found", path);
|
||||||
notfound(r);
|
notfound(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
clicon_debug(1, "NULL URI");
|
clicon_debug(1, "NULL URI");
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ api_data_get(clicon_handle h,
|
||||||
return api_data_get2(h, r, pcvec, pi, qvec, 0);
|
return api_data_get2(h, r, pcvec, pi, qvec, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! REST POST method
|
/*! Generic REST POST method
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
|
* @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] pi Offset, where to start pcvec
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
* @note We map post to edit-config create.
|
* @note restconf POST is mapped to edit-config create.
|
||||||
POST:
|
POST:
|
||||||
target resource type is datastore --> create a top-level resource
|
target resource type is datastore --> create a top-level resource
|
||||||
target resource type is data resource --> create child resource
|
target resource type is data resource --> create child resource
|
||||||
|
|
@ -414,12 +414,12 @@ api_data_post(clicon_handle h,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *data)
|
char *data)
|
||||||
{
|
{
|
||||||
enum operation_type op = OP_CREATE;
|
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
enum operation_type op = OP_CREATE;
|
||||||
int i;
|
int i;
|
||||||
cxobj *xdata = NULL;
|
cxobj *xdata = NULL;
|
||||||
cxobj *xtop = NULL; /* xpath root */
|
|
||||||
cbuf *cbx = NULL;
|
cbuf *cbx = NULL;
|
||||||
|
cxobj *xtop = NULL; /* xpath root */
|
||||||
cxobj *xbot = NULL;
|
cxobj *xbot = NULL;
|
||||||
cxobj *x;
|
cxobj *x;
|
||||||
yang_node *y = NULL;
|
yang_node *y = NULL;
|
||||||
|
|
@ -444,8 +444,8 @@ api_data_post(clicon_handle h,
|
||||||
/* Create config top-of-tree */
|
/* Create config top-of-tree */
|
||||||
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
/* Translate api_path to xtop/xbot */
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
/* xbot is resulting xml tree on exit */
|
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Parse input data as json or xml into xml */
|
/* 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){
|
else if (json_parse_str(data, &xdata) < 0){
|
||||||
badrequest(r);
|
badrequest(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
/* Add xdata to xbot */
|
/* The message-body MUST contain exactly one instance of the
|
||||||
x = NULL;
|
* expected data resource.
|
||||||
while ((x = xml_child_each(xdata, x, CX_ELMNT)) != NULL) {
|
*/
|
||||||
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
if (xml_child_nr(xdata) != 1){
|
||||||
goto done;
|
badrequest(r);
|
||||||
xml_type_set(xa, CX_ATTR);
|
goto ok;
|
||||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (xml_addsub(xbot, x) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
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)
|
if ((cbx = cbuf_new()) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
|
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
|
||||||
goto done;
|
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",
|
if (clicon_rpc_edit_config(h, "candidate",
|
||||||
OP_NONE,
|
OP_NONE,
|
||||||
cbuf_get(cbx)) < 0){
|
cbuf_get(cbx)) < 0){
|
||||||
// notfound(r); /* XXX */
|
|
||||||
conflict(r);
|
conflict(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -492,7 +498,6 @@ api_data_post(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
FCGX_SetExitStatus(201, r->out); /* Created */
|
FCGX_SetExitStatus(201, r->out); /* Created */
|
||||||
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
|
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");
|
FCGX_FPrintF(r->out, "\r\n");
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -505,6 +510,58 @@ api_data_post(clicon_handle h,
|
||||||
if (cbx)
|
if (cbx)
|
||||||
cbuf_free(cbx);
|
cbuf_free(cbx);
|
||||||
return retval;
|
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
|
/*! Generic REST PUT method
|
||||||
|
|
@ -515,6 +572,7 @@ api_data_post(clicon_handle h,
|
||||||
* @param[in] pi Offset, where to start pcvec
|
* @param[in] pi Offset, where to start pcvec
|
||||||
* @param[in] qvec Vector of query string (QUERY_STRING)
|
* @param[in] qvec Vector of query string (QUERY_STRING)
|
||||||
* @param[in] data Stream input data
|
* @param[in] data Stream input data
|
||||||
|
* @note restconf PUT is mapped to edit-config replace.
|
||||||
* @example
|
* @example
|
||||||
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
|
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
|
int
|
||||||
api_data_put(clicon_handle h,
|
api_data_put(clicon_handle h,
|
||||||
FCGX_Request *r,
|
FCGX_Request *r,
|
||||||
char *api_path,
|
char *api_path0,
|
||||||
cvec *pcvec,
|
cvec *pcvec,
|
||||||
int pi,
|
int pi,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
|
|
@ -539,19 +597,19 @@ api_data_put(clicon_handle h,
|
||||||
int i;
|
int i;
|
||||||
cxobj *xdata = NULL;
|
cxobj *xdata = NULL;
|
||||||
cbuf *cbx = NULL;
|
cbuf *cbx = NULL;
|
||||||
cxobj *x;
|
cxobj *xtop = NULL; /* xpath root */
|
||||||
cxobj *xbot = NULL;
|
cxobj *xbot = NULL;
|
||||||
cxobj *xtop = NULL;
|
cxobj *xparent;
|
||||||
cxobj *xp;
|
cxobj *x;
|
||||||
yang_node *y = NULL;
|
yang_node *y = NULL;
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
cxobj *xa;
|
cxobj *xa;
|
||||||
char *media_content_type;
|
char *media_content_type;
|
||||||
int parse_xml = 0; /* By default expect and parse JSON */
|
int parse_xml = 0; /* By default expect and parse JSON */
|
||||||
|
char *api_path;
|
||||||
|
|
||||||
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
|
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
|
||||||
__FUNCTION__,
|
__FUNCTION__, api_path0, data);
|
||||||
api_path, data);
|
|
||||||
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
|
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
|
||||||
if (media_content_type &&
|
if (media_content_type &&
|
||||||
strcmp(media_content_type, "application/yang-data+xml")==0)
|
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");
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
api_path=api_path0;
|
||||||
for (i=0; i<pi; i++)
|
for (i=0; i<pi; i++)
|
||||||
api_path = index(api_path+1, '/');
|
api_path = index(api_path+1, '/');
|
||||||
/* Create config top-of-tree */
|
/* Create config top-of-tree */
|
||||||
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
/* Translate api_path to xtop/xbot */
|
||||||
xbot = xtop;
|
xbot = xtop;
|
||||||
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -579,22 +639,48 @@ api_data_put(clicon_handle h,
|
||||||
badrequest(r);
|
badrequest(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
/* The message-body MUST contain exactly one instance of the
|
||||||
|
* expected data resource.
|
||||||
|
*/
|
||||||
if (xml_child_nr(xdata) != 1){
|
if (xml_child_nr(xdata) != 1){
|
||||||
badrequest(r);
|
badrequest(r);
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
x = xml_child_i(xdata,0);
|
x = xml_child_i(xdata,0);
|
||||||
|
/* Add operation (create/replace) as attribute */
|
||||||
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
xml_type_set(xa, CX_ATTR);
|
xml_type_set(xa, CX_ATTR);
|
||||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* XXX Special case path=/restconf/data xml_name(x) == data */
|
#if 1 /* This is different from POST */
|
||||||
/* Replace xbot with x */
|
/* Replace xparent with x, ie bottom of api-path with data */
|
||||||
xp = xml_parent(xbot);
|
if (api_path==NULL && strcmp(xml_name(x),"data")==0){
|
||||||
xml_purge(xbot);
|
if (xml_addsub(NULL, x) < 0)
|
||||||
if (xml_addsub(xp, x) < 0)
|
goto done;
|
||||||
goto done;
|
xtop = x;
|
||||||
|
xml_name_set(xtop, "config");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Check same symbol in api-path as data */
|
||||||
|
if (strcmp(xml_name(x), xml_name(xbot))){
|
||||||
|
badrequest(r);
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* If list or leaf-list, api-path keys must match data keys */
|
||||||
|
if (y && (y->yn_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)
|
if ((cbx = cbuf_new()) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
|
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
|
||||||
|
|
@ -628,8 +714,7 @@ api_data_put(clicon_handle h,
|
||||||
if (cbx)
|
if (cbx)
|
||||||
cbuf_free(cbx);
|
cbuf_free(cbx);
|
||||||
return retval;
|
return retval;
|
||||||
|
} /* api_data_put */
|
||||||
}
|
|
||||||
|
|
||||||
/*! Generic REST PATCH method
|
/*! Generic REST PATCH method
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
|
|
@ -724,6 +809,20 @@ api_data_delete(clicon_handle h,
|
||||||
return retval;
|
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
|
/*! REST operation POST method
|
||||||
* @param[in] h CLIXON handle
|
* @param[in] h CLIXON handle
|
||||||
* @param[in] r Fastcgi request handle
|
* @param[in] r Fastcgi request handle
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,10 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
|
||||||
cvec *qvec, char *data);
|
cvec *qvec, char *data);
|
||||||
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi);
|
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,
|
int api_operation_post(clicon_handle h, FCGX_Request *r,
|
||||||
char *path,
|
char *path,
|
||||||
cvec *pcvec, int pi, cvec *qvec, char *data);
|
cvec *pcvec, int pi, cvec *qvec, char *data);
|
||||||
|
|
|
||||||
|
|
@ -748,14 +748,19 @@ text_modify_top(cxobj *x0,
|
||||||
cxobj *x0c; /* base child */
|
cxobj *x0c; /* base child */
|
||||||
cxobj *x1c; /* mod child */
|
cxobj *x1c; /* mod child */
|
||||||
yang_stmt *yc; /* yang child */
|
yang_stmt *yc; /* yang child */
|
||||||
|
char *opstr;
|
||||||
|
|
||||||
/* Assure top-levels are 'config' */
|
/* Assure top-levels are 'config' */
|
||||||
assert(x0 && strcmp(xml_name(x0),"config")==0);
|
assert(x0 && strcmp(xml_name(x0),"config")==0);
|
||||||
assert(x1 && strcmp(xml_name(x1),"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 <config/> */
|
/* Special case if x1 is empty, top-level only <config/> */
|
||||||
if (!xml_child_nr(x1)){ /* base tree not empty */
|
if (!xml_child_nr(x1)){
|
||||||
if (xml_child_nr(x0))
|
if (xml_child_nr(x0)) /* base tree not empty */
|
||||||
switch(op){
|
switch(op){
|
||||||
case OP_DELETE:
|
case OP_DELETE:
|
||||||
case OP_REMOVE:
|
case OP_REMOVE:
|
||||||
|
|
|
||||||
|
|
@ -1654,9 +1654,9 @@ api_path2xml(char *api_path,
|
||||||
if (nvec > 1 && !strlen(vec[nvec-1]))
|
if (nvec > 1 && !strlen(vec[nvec-1]))
|
||||||
nvec--;
|
nvec--;
|
||||||
if (nvec < 1){
|
if (nvec < 1){
|
||||||
clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
|
clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
nvec--; /* NULL-terminated */
|
nvec--; /* NULL-terminated */
|
||||||
if (api_path2xml_vec(vec+1, nvec,
|
if (api_path2xml_vec(vec+1, nvec,
|
||||||
xpath, (yang_node*)yspec, schemanode,
|
xpath, (yang_node*)yspec, schemanode,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Transactions per second for large lists read/write plotter using gnuplot
|
# Transactions per second for large lists read/write plotter using gnuplot
|
||||||
#
|
#
|
||||||
. ./lib.sh
|
. ./lib.sh
|
||||||
max=1000 # Nr of db entries
|
max=200 # Nr of db entries
|
||||||
step=100
|
step=100
|
||||||
reqs=1000
|
reqs=1000
|
||||||
cfg=$dir/scaling-conf.xml
|
cfg=$dir/scaling-conf.xml
|
||||||
|
|
@ -48,27 +48,55 @@ EOF
|
||||||
run(){
|
run(){
|
||||||
nr=$1 # Number of entries in DB
|
nr=$1 # Number of entries in DB
|
||||||
reqs=$2
|
reqs=$2
|
||||||
write=$3
|
mode=$3
|
||||||
|
|
||||||
echo -n "<rpc><edit-config><target><candidate/></target><config><x>" > $fconfig
|
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x>" > $fconfig
|
||||||
for (( i=0; i<$nr; i++ )); do
|
for (( i=0; i<$nr; i++ )); do
|
||||||
|
echo -n "<c>$i</c>" >> $fconfig
|
||||||
echo -n "<y><a>$i</a><b>$i</b></y>" >> $fconfig
|
echo -n "<y><a>$i</a><b>$i</b></y>" >> $fconfig
|
||||||
done
|
done
|
||||||
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
|
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
|
||||||
|
|
||||||
expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof_file "$clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
if $write; then
|
case $mode in
|
||||||
time -p for (( i=0; i<$reqs; i++ )); do
|
readlist)
|
||||||
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>]]>]]>"
|
|
||||||
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
|
||||||
else # read
|
|
||||||
time -p for (( i=0; i<$reqs; i++ )); do
|
time -p for (( i=0; i<$reqs; i++ )); do
|
||||||
rnd=$(( ( RANDOM % $nr ) ))
|
rnd=$(( ( RANDOM % $nr ) ))
|
||||||
echo "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=$rnd][b=$rnd]\" /></get-config></rpc>]]>]]>"
|
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
|
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||||
fi
|
;;
|
||||||
|
writelist)
|
||||||
|
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>]]>]]>"
|
||||||
|
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||||
|
;;
|
||||||
|
readleaflist)
|
||||||
|
time -p for (( i=0; i<$reqs; i++ )); do
|
||||||
|
rnd=$(( ( RANDOM % $nr ) ))
|
||||||
|
echo "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x[c=$rnd]\" /></get-config></rpc>]]>]]>"
|
||||||
|
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||||
|
;;
|
||||||
|
writeleaflist)
|
||||||
|
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>]]>]]>"
|
||||||
|
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
expecteof "$clixon_netconf -qf $cfg" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
}
|
||||||
|
|
||||||
|
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()(
|
once()(
|
||||||
|
|
@ -84,18 +112,19 @@ once()(
|
||||||
err
|
err
|
||||||
fi
|
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
|
# Actual steps
|
||||||
for (( i=$step; i<=$max; i=i+$step )); do
|
for (( i=$step; i<=$max; i=i+$step )); do
|
||||||
t=$(TEST=%e run $i $reqs true $ 2>&1 | awk '/real/ {print $2}')
|
step $i readlist
|
||||||
# t is time in secs of $reqs -> transactions per second. $reqs
|
step $i readleaflist
|
||||||
p=$(echo "$reqs/$t" | bc -lq)
|
step $i writelist
|
||||||
# p is transactions per second.
|
step $i writeleaflist
|
||||||
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
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check if still alive
|
# 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 xlabel "entries"
|
||||||
set ylabel "transactions per second"
|
set ylabel "transactions per second"
|
||||||
set terminal wxt enhanced title "CLixon transactions " persist raise
|
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
|
EOF
|
||||||
|
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
|
|
|
||||||
|
|
@ -88,15 +88,15 @@ expectfn "curl -sS -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
|
||||||
new "restconf root discovery"
|
new "restconf root discovery"
|
||||||
expectfn "curl -sS -X GET http://localhost/.well-known/host-meta" "<Link rel='restconf' href='/restconf'/>"
|
expectfn "curl -sS -X GET http://localhost/.well-known/host-meta" "<Link rel='restconf' href='/restconf'/>"
|
||||||
|
|
||||||
|
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"
|
new "restconf empty rpc"
|
||||||
expectfn 'curl -sS -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}'
|
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"
|
new "restconf get empty config + state json"
|
||||||
expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}"
|
expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}"
|
||||||
|
|
||||||
|
|
|
||||||
141
test/test_restconf2.sh
Executable file
141
test/test_restconf2.sh
Executable file
|
|
@ -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
|
||||||
|
|
||||||
|
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||||
|
cat <<EOF > $cfg
|
||||||
|
<config>
|
||||||
|
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||||
|
<CLICON_YANG_DIR>/usr/local/var</CLICON_YANG_DIR>
|
||||||
|
<CLICON_YANG_MODULE_MAIN>$fyang</CLICON_YANG_MODULE_MAIN>
|
||||||
|
|
||||||
|
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||||
|
<CLICON_SOCK>/usr/local/var/routing/routing.sock</CLICON_SOCK>
|
||||||
|
<CLICON_BACKEND_PIDFILE>/usr/local/var/routing/routing.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
|
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||||
|
<CLICON_XMLDB_DIR>/usr/local/var/routing</CLICON_XMLDB_DIR>
|
||||||
|
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
||||||
|
</config>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > $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
|
||||||
242
yang/ietf-yang-library@2016-06-21.yang
Normal file
242
yang/ietf-yang-library@2016-06-21.yang
Normal file
|
|
@ -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: <https://datatracker.ietf.org/wg/netconf/>
|
||||||
|
WG List: <mailto:netconf@ietf.org>
|
||||||
|
|
||||||
|
WG Chair: Mehmet Ersue
|
||||||
|
<mailto:mehmet.ersue@nsn.com>
|
||||||
|
|
||||||
|
WG Chair: Mahesh Jethanandani
|
||||||
|
<mailto:mjethanandani@gmail.com>
|
||||||
|
|
||||||
|
Editor: Andy Bierman
|
||||||
|
<mailto:andy@yumaworks.com>
|
||||||
|
|
||||||
|
Editor: Martin Bjorklund
|
||||||
|
<mailto:mbj@tail-f.com>
|
||||||
|
|
||||||
|
Editor: Kent Watsen
|
||||||
|
<mailto:kwatsen@juniper.net>";
|
||||||
|
|
||||||
|
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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue