- removed api_path extension from internal netconf

- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
This commit is contained in:
Olof hagsand 2017-05-30 21:57:06 +02:00
parent 683376610c
commit 3453dae0db
36 changed files with 1048 additions and 1307 deletions

View file

@ -1,5 +1,8 @@
# Clixon CHANGELOG
- removed api_path extension from internal netconf
- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
## 3.3.0
May 2017

View file

@ -256,8 +256,6 @@ from_client_get_config(clicon_handle h,
* @param[in] xe Netconf request xml tree
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer
* CLIXON addition:
* <filter type="restconf" select="/data/profile=a" />
*/
static int
from_client_edit_config(clicon_handle h,
@ -270,10 +268,8 @@ from_client_edit_config(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xc;
cxobj *xfilter;
cxobj *x;
enum operation_type operation = OP_MERGE;
char *api_path = NULL;
int piddb;
if ((target = netconf_db_find(xn, "target")) == NULL){
@ -293,9 +289,6 @@ from_client_edit_config(clicon_handle h,
piddb);
goto ok;
}
/* ie <filter type="restconf" select=<api-path> /> */
if ((xfilter = xpath_first(xn, "filter")) != NULL)
api_path = xml_find_value(xfilter, "select");
if ((x = xpath_first(xn, "default-operation")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
@ -307,7 +300,7 @@ from_client_edit_config(clicon_handle h,
}
}
if ((xc = xpath_first(xn, "config")) != NULL){
if (xmldb_put(h, target, operation, api_path, xc) < 0){
if (xmldb_put(h, target, operation, xc) < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>protocol</error-type>"

View file

@ -194,7 +194,7 @@ rundb_main(clicon_handle h,
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
if (xmldb_put(h, "tmp", OP_MERGE, NULL, xn) < 0)
if (xmldb_put(h, "tmp", OP_MERGE, xn) < 0)
goto done;
if (candidate_commit(h, "tmp") < 0)
goto done;

View file

@ -207,38 +207,64 @@ cli_dbxml(clicon_handle h,
{
int retval = -1;
char *str = NULL;
char *xkfmt; /* xml key format */
char *xk = NULL; /* xml key */
char *api_path_fmt; /* xml key format */
char *api_path = NULL; /* xml key */
cg_var *cval;
int len;
cg_var *arg;
cbuf *cb = NULL;
yang_spec *yspec;
cxobj *xbot = NULL; /* xpath, NULL if datastore */
yang_node *y = NULL; /* yang spec of xpath */
cxobj *xtop = NULL; /* xpath root */
cxobj *xa; /* attribute */
cxobj *xb; /* body */
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
goto done;
}
arg = cvec_i(argv, 0);
xkfmt = cv_string_get(arg);
if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
len = cvec_len(cvv);
if (len > 1){
cval = cvec_i(cvv, len-1);
if ((str = cv2str_dup(cval)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
arg = cvec_i(argv, 0);
api_path_fmt = cv_string_get(arg);
if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0)
goto done;
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
goto done;
if ((xa = xml_new("operation", xbot)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if (y->yn_keyword != Y_LIST){
len = cvec_len(cvv);
if (len > 1){
cval = cvec_i(cvv, len-1);
if ((str = cv2str_dup(cval)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if ((xb = xml_new("body", xbot)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, str) < 0)
goto done;
}
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (str)
cprintf(cb, "<config>%s</config>", str);
else
cprintf(cb, "<config/>");
if (clicon_rpc_edit_config(h, "candidate", op, xk, cbuf_get(cb)) < 0)
if (clicon_xml2cbuf(cb, xtop, 0, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
goto done;
if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h) < 0)
@ -250,8 +276,10 @@ cli_dbxml(clicon_handle h,
cbuf_free(cb);
if (str)
free(str);
if (xk)
free(xk);
if (api_path)
free(api_path);
if (xtop)
xml_free(xtop);
return retval;
}
@ -287,6 +315,31 @@ int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
return cli_merge(h, vars, argv);
}
int
cli_create(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxml(h, cvv, argv, OP_CREATE) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*!
* @see cli_del
*/
int
cli_remove(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
if (cli_dbxml(h, cvv, argv, OP_REMOVE) < 0)
goto done;
retval = 0;
done:
return retval;
}
int
cli_del(clicon_handle h, cvec *cvv, cvec *argv)
@ -704,7 +757,6 @@ load_config_file(clicon_handle h,
}
if (clicon_rpc_edit_config(h, "candidate",
replace?OP_REPLACE:OP_MERGE,
"",
cbuf_get(cbxml)) < 0)
goto done;
cbuf_free(cbxml);
@ -1135,7 +1187,7 @@ cli_copy_config(clicon_handle h,
cbuf_reset(cb);
/* create xml copy tree and merge it with database configuration */
clicon_xml2cbuf(cb, x2, 0, 0);
if (clicon_rpc_edit_config(h, db, OP_MERGE, NULL, cbuf_get(cb)) < 0)
if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0)
goto done;
retval = 0;
done:

View file

@ -64,7 +64,7 @@
#include "cli_generate.h"
/* This is the default callback function. But this is typically overwritten */
#define GENERATE_CALLBACK "cli_set"
#define GENERATE_CALLBACK "overwrite_me"
/* variable expand function */
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
@ -115,13 +115,12 @@ cli_expand_var_generate(clicon_handle h,
enum cv_type cvtype,
cbuf *cb0,
int options,
uint8_t fraction_digits
)
uint8_t fraction_digits)
{
int retval = -1;
char *xkfmt = NULL;
char *api_path_fmt = NULL;
if (yang2xmlkeyfmt(ys, 1, &xkfmt) < 0)
if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
goto done;
cprintf(cb0, "|<%s:%s", ys->ys_argument,
cv_type2str(cvtype));
@ -129,15 +128,15 @@ cli_expand_var_generate(clicon_handle h,
cprintf(cb0, " fraction-digits:%u", fraction_digits);
cprintf(cb0, " %s(\"candidate\",\"%s\")>",
GENERATE_EXPAND_XMLDB,
xkfmt);
api_path_fmt);
retval = 0;
done:
if (xkfmt)
free(xkfmt);
if (api_path_fmt)
free(api_path_fmt);
return retval;
}
/*! Create callback with xmlkey format string as argument
/*! Create callback with api_path format string as argument
* @param[in] h clicon handle
* @param[in] ys yang_stmt of the node at hand
* @param[in] cb0 The string where the result format string is inserted.
@ -149,15 +148,16 @@ cli_callback_generate(clicon_handle h,
cbuf *cb0)
{
int retval = -1;
char *xkfmt = NULL;
char *api_path_fmt = NULL;
if (yang2xmlkeyfmt(ys, 0, &xkfmt) < 0)
if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
goto done;
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK,
api_path_fmt);
retval = 0;
done:
if (xkfmt)
free(xkfmt);
if (api_path_fmt)
free(api_path_fmt);
return retval;
}
@ -166,8 +166,7 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
enum genmodel_type gt,
int level);
/*
* Check for completion (of already existent values), ranges (eg range[min:max]) and
/*! Check for completion (of already existent values), ranges (eg range[min:max]) and
* patterns, (eg regexp:"[0.9]*").
*/
static int
@ -403,7 +402,6 @@ yang2cli_leaf(clicon_handle h,
return retval;
}
static int
yang2cli_container(clicon_handle h,
yang_stmt *ys,

View file

@ -99,10 +99,10 @@ expand_dbvar(void *h,
cvec *helptexts)
{
int retval = -1;
char *xkfmt;
char *api_path;
char *dbstr;
cxobj *xt = NULL;
char *xkpath = NULL;
char *xpath = NULL;
cxobj **xvec = NULL;
size_t xlen = 0;
cxobj *x;
@ -129,23 +129,24 @@ expand_dbvar(void *h,
goto done;
}
if ((cv = cvec_i(argv, 1)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <xkfmt>");
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <api_path>");
goto done;
}
xkfmt = cv_string_get(cv);
/* xkfmt = /interface/%s/address/%s
api_path = cv_string_get(cv);
/* api_path = /interface/%s/address/%s
--> ^/interface/eth0/address/.*$
--> /interface/[name=eth0]/address
*/
if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
if (api_path_fmt2xpath(api_path, cvv, &xpath) < 0)
goto done;
/* XXX read whole configuration, why not send xpath? */
if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
j = 0;
if (xpath_vec(xt, xkpath, &xvec, &xlen) < 0)
if (xpath_vec(xt, xpath, &xvec, &xlen) < 0)
goto done;
for (i = 0; i < xlen; i++) {
char *str;
@ -190,8 +191,8 @@ expand_dbvar(void *h,
free(xvec);
if (xt)
xml_free(xt);
if (xkpath)
free(xkpath);
if (xpath)
free(xpath);
return retval;
}
int

View file

@ -82,6 +82,9 @@ int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
int cli_create(clicon_handle h, cvec *vars, cvec *argv);
int cli_remove(clicon_handle h, cvec *vars, cvec *argv);
int cli_del(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv);

View file

@ -67,8 +67,7 @@ curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"et
Start the restconf fastcgi program with debug flag:
```
sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-
data
sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf" -s /bin/sh www-data
```
Look at syslog:
```

View file

@ -69,11 +69,12 @@ notfound(FCGX_Request *r)
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Clixon Not Found</h1>\n");
FCGX_FPrintF(r->out, "<h1>Not Found</h1>\n");
FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n",
path);
return 0;
}
int
badrequest(FCGX_Request *r)
{
@ -89,6 +90,16 @@ badrequest(FCGX_Request *r)
return 0;
}
int
conflict(FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Status: 409\r\n"); /* 409 Conflict */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Data resource already exists</h1>\n");
return 0;
}
/*! Specialization of clicon_debug with xml tree */
int
clicon_debug_xml(int dbglevel,

View file

@ -45,6 +45,7 @@
*/
int notfound(FCGX_Request *r);
int badrequest(FCGX_Request *r);
int conflict(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r);

View file

@ -141,7 +141,7 @@ request_process(clicon_handle h,
int auth = 0;
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
path = FCGX_GetParam("REQUEST_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
@ -359,7 +359,7 @@ main(int argc,
goto done;
}
clicon_debug(1, "------------");
if ((path = FCGX_GetParam("DOCUMENT_URI", r->envp)) != NULL){
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);

View file

@ -34,8 +34,7 @@
*/
/*
* See draft-ietf-netconf-restconf-13.txt [draft]
* See draft-ietf-netconf-restconf-17.txt [draft]
* See rfc8040
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
@ -120,7 +119,7 @@ Mapping netconf error-tag -> status code
#include "restconf_methods.h"
/*! REST OPTIONS method
* According to restconf (Sec 3.5.1.1 in [draft])
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @code
@ -227,7 +226,7 @@ api_data_head(clicon_handle h,
}
/*! REST GET method
* According to restconf (Sec 3.5.1.1 in [draft])
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
@ -259,77 +258,23 @@ api_data_get(clicon_handle h,
return api_data_get_gen(h, r, pcvec, pi, qvec, 0);
}
/*! Generic edit-config method: PUT/POST/PATCH
*/
static int
api_data_edit(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
enum operation_type operation)
{
int retval = -1;
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
cxobj *x;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__,
api_path, data);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<config>");
x = NULL;
while ((x = xml_child_each(xdata, x, -1)) != NULL) {
if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
goto done;
}
cprintf(cbx, "</config>");
clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_edit_config(h, "candidate",
operation,
api_path,
cbuf_get(cbx)) < 0){
notfound(r);
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @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.
POST:
target resource type is datastore --> create a top-level resource
target resource type is data resource --> create child resource
The message-body MUST contain exactly one instance of the
expected data resource. The data model for the child tree is the
subtree, as defined by YANG for the child resource.
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
@ -338,7 +283,6 @@ api_data_edit(clicon_handle h,
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
* Netconf: <edit-config> (nc:operation="create") | invoke an RPC operation * @example
*/
int
api_data_post(clicon_handle h,
@ -349,13 +293,91 @@ api_data_post(clicon_handle h,
cvec *qvec,
char *data)
{
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_CREATE);
enum operation_type op = OP_CREATE;
int retval = -1;
int i;
cxobj *xdata = NULL;
cxobj *xtop = NULL; /* xpath root */
cbuf *cbx = NULL;
cxobj *xbot = NULL;
cxobj *x;
yang_node *y = NULL;
yang_spec *yspec;
cxobj *xa;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__,
api_path, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
goto done;
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
/* Add xdata to xbot */
x = NULL;
while ((x = xml_child_each(xdata, x, CX_ELMNT)) != NULL) {
if ((xa = xml_new("operation", x)) == 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;
}
xdata = NULL;
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));
if (clicon_rpc_edit_config(h, "candidate",
OP_NONE,
cbuf_get(cbx)) < 0){
// notfound(r); /* XXX */
conflict(r);
goto done;
}
if (clicon_rpc_validate(h, "candidate") < 0){
if (clicon_rpc_discard_changes(h) < 0)
goto done;
badrequest(r);
retval = 0;
goto done;
}
if (clicon_rpc_commit(h) < 0)
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");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xtop)
xml_free(xtop);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Generic REST PUT method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
@ -379,14 +401,96 @@ api_data_put(clicon_handle h,
cvec *qvec,
char *data)
{
/* XXX: OP_CREATE? */
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_REPLACE);
int retval = -1;
enum operation_type op = OP_REPLACE;
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
cxobj *x;
cxobj *xbot = NULL;
cxobj *xtop;
cxobj *xp;
yang_node *y = NULL;
yang_spec *yspec;
cxobj *xa;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__,
api_path, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
goto done;
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
if (xml_child_nr(xdata) != 1){
badrequest(r);
retval = 0;
goto done;
}
x = xml_child_i(xdata,0);
if ((xa = xml_new("operation", x)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
/* Replace xbot with x */
xp = xml_parent(xbot);
xml_purge(xbot);
if (xml_addsub(xp, x) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
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);
goto done;
}
if (clicon_rpc_validate(h, "candidate") < 0){
if (clicon_rpc_discard_changes(h) < 0)
goto done;
badrequest(r);
retval = 0;
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xtop)
xml_free(xtop);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
@ -402,13 +506,15 @@ api_data_patch(clicon_handle h,
cvec *qvec,
char *data)
{
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
badrequest(r);
// return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
return 0;
}
/*! Generic REST DELETE method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
@ -422,14 +528,39 @@ api_data_delete(clicon_handle h,
{
int retval = -1;
int i;
cxobj *xtop = NULL; /* xpath root */
cxobj *xbot = NULL;
cxobj *xa;
cbuf *cbx = NULL;
yang_node *y = NULL;
yang_spec *yspec;
enum operation_type op = OP_DELETE;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
goto done;
if ((xa = xml_new("operation", xbot)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate",
OP_DELETE,
api_path,
"<config/>") < 0){
OP_NONE,
cbuf_get(cbx)) < 0){
notfound(r);
goto done;
}
@ -440,6 +571,10 @@ api_data_delete(clicon_handle h,
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
if (xtop)
xml_free(xtop);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}

View file

@ -43,7 +43,7 @@ AC_INIT(lib/clixon/clixon.h.in)
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="3"
CLIXON_VERSION_PATCH="0"
CLIXON_VERSION_PATCH="1"
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
# Fix to specific version (eg 3.5) or head (3)
CLIGEN_VERSION="3"

View file

@ -89,7 +89,7 @@ usage(char *argv0)
"\t-m <module>\tYang module. Mandatory\n"
"and command is either:\n"
"\tget <xpath>\n"
"\tput (merge|replace|create|delete|remove) <api_path> <xml>\n"
"\tput (merge|replace|create|delete|remove) <xml>\n"
"\tcopy <todb>\n"
"\tlock <pid>\n"
"\tunlock\n"
@ -220,7 +220,7 @@ main(int argc, char **argv)
fprintf(stdout, "\n");
}
else if (strcmp(cmd, "put")==0){
if (argc != 3 && argc != 4){
if (argc != 3){
clicon_err(OE_DB, 0, "Unexpected nr of args: %d", argc);
usage(argv0);
}
@ -228,13 +228,11 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0);
}
if (argc == 4){
if (clicon_xml_parse_str(argv[3], &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
}
if (xmldb_put(h, db, op, argv[2], xt) < 0)
if (clicon_xml_parse_str(argv[2], &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if (xmldb_put(h, db, op, xt) < 0)
goto done;
}
else if (strcmp(cmd, "copy")==0){

View file

@ -65,7 +65,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean:
rm -f $(PLUGIN) $(OBJS) *.core
@ -81,12 +81,12 @@ distclean: clean
install: $(PLUGIN)
install -d $(DESTDIR)$(clixon_LIBDIR)/xmldb
install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb;
install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb
install-include:
uninstall:
rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN);
rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -

View file

@ -50,15 +50,15 @@
* The relations between the functions and formats are as follows:
*
* +-----------------+ +-----------------+
* | yang-stmt | yang2xmlkeyfmt | xmlkeyfmt | xmlkeyfmt2xpath
* | yang-stmt | yang2api_path_fmt | api_path_fmt | api_path_fmt2xpath
* | list aa,leaf k | ----------------->| /aa=%s |---------------->
* +-----------------+ +-----------------+
* |
* | xmlkeyfmt2key
* | api_path_fmt2api_path
* | k=17
* v
* +-------------------+ +-----------------+
* | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986|
* | xml-tree/cxobj | xmlkey2xml |api_path RFC3986|
* | <aa><k>17</k></aa>| <------------- | /aa=17 |
* +-------------------+ +-----------------+
*
@ -68,7 +68,7 @@
*
* Paths through the code (for coverage)
* cli_callback_generate +----------------+
* cli_expand_var_generate | yang2xmlkeyfmt |
* cli_expand_var_generate | yang2api_path_fmt |
* yang -------------> | |
* +----------------+
* xmldb_get_tree
@ -785,494 +785,6 @@ put(char *dbfile,
return retval;
}
/*! Modify database provided an XML database key and an operation
* @param[in] kh Keyvalue handle
* @param[in] db Database name
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] xk XML Key, eg /aa/bb=17/name
* @param[in] val Key value, eg "17"
* @retval 0 OK
* @retval -1 Error
* @code
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
static int
xmldb_put_xkey(struct kv_handle *kh,
char *db,
enum operation_type op,
char *xk,
char *val)
{
int retval = -1;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec = NULL;
int nvec;
char **valvec = NULL;
int nvalvec;
int i;
int j;
char *name;
char *restval;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2 = NULL;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
char *filename = NULL;
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
if ((yspec = kh->kh_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (kv_db2file(kh, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid api_path: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
cprintf(ckey, "=%s", restval);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if (valvec)
free(valvec);
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Iterate over individual yang keys */
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
xk = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
goto done;
}
else
if (db_set(filename, xk, NULL, 0) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exist in database", xk);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", xk);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
if (vec)
free(vec);
if (valvec)
free(valvec);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Modify database provided an xml tree, a restconf api_path and an operation
*
* @param[in] kh Keyvalue handle
* @param[in] db running or candidate
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* @param[in] xt xml-tree. Top-level symbol is dummy
* @retval 0 OK
* @retval -1 Error
* example:
* container top {
* list list1 {
* key "key1 key2 key3";
* is referenced as
* /restconf/data/top/list1=a,,foo
* @see xmldb_put
*/
static int
xmldb_put_restconf_api_path(struct kv_handle *kh,
char *db,
enum operation_type op,
char *xk,
cxobj *xt)
{
int retval = -1;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec = NULL;
int nvec;
#if 0
char **valvec = NULL;
int nvalvec;
int j;
char *restval;
#endif
int i;
char *name;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
yang_stmt *ys;
char *filename = NULL;
char *key;
char *keys;
if ((yspec = kh->kh_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (kv_db2file(kh, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid api path: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i];
if ((keys = index(name, '=')) != NULL){
*keys = '\0';
keys++;
}
#if 0
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
#endif
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
/* For leaf-list 'keys' is value, see 3.5.1 in restconf draft */
val2 = keys;
cprintf(ckey, "/%s", keys);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
#if 0
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if (valvec)
free(valvec);
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
j = 0;
#endif
cvi = NULL;
/* Iterate over individual yang keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
#if 0
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
#else
// val2 = vec[i++]; /* No */
val2 = keys;
if (i>nvec){ /* XXX >= ? */
clicon_err(OE_XML, errno, "List %s without argument", name);
goto done;
}
cprintf(ckey, "=%s", val2);
#endif
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
key = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, key)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (xt==NULL){
clicon_err(OE_DB, 0, "%s: no xml when yang node %s required",
__FUNCTION__, y->ys_argument);
goto done;
}
if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){
clicon_err(OE_DB, 0, "%s: child %s not found under node %s",
__FUNCTION__, xml_name(xt), y->ys_argument);
goto done;
}
y = ys;
if (put(filename, xt, y, op, key) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, key)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", key);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
// clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
if (vec)
free(vec);
#if 0
if (valvec)
free(valvec);
#endif
unchunk_group(__FUNCTION__);
return retval;
}
/*! Modify database provided an xml tree and an operation
*
@ -1299,7 +811,6 @@ int
kv_put(xmldb_handle xh,
char *db,
enum operation_type op,
char *api_path,
cxobj *xt)
{
int retval = -1;
@ -1309,9 +820,6 @@ kv_put(xmldb_handle xh,
yang_spec *yspec;
char *dbfilename = NULL;
if (xt && (xml_child_nr(xt)==0 || xml_body(xt)!= NULL) &&
api_path && strlen(api_path) && strcmp(api_path,"/"))
return xmldb_put_xkey(kh, db, op, api_path, xml_body(xt));
if ((yspec = kh->kh_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
@ -1324,28 +832,19 @@ kv_put(xmldb_handle xh,
if (db_init(dbfilename) < 0)
goto done;
}
if (api_path && strlen(api_path) && strcmp(api_path,"/")){
// clicon_log(LOG_WARNING, "xmldb_put_restconf_api_path");
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if (xmldb_put_restconf_api_path(kh, db, op, api_path, x) < 0)
goto done;
}
}
else{
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
goto done;
}
if (put(dbfilename, /* database name */
x, /* xml root node */
ys, /* yang statement of xml node */
op, /* operation, eg merge/delete */
"" /* aggregate xml key */
) < 0)
goto done;
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
goto done;
}
if (put(dbfilename, /* database name */
x, /* xml root node */
ys, /* yang statement of xml node */
op, /* operation, eg merge/delete */
"" /* aggregate xml key */
) < 0)
goto done;
}
retval = 0;
done:

View file

@ -41,8 +41,7 @@
*/
int kv_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int kv_put(xmldb_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt);
int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int kv_dump(FILE *f, char *dbfilename, char *rxkey);
int kv_copy(xmldb_handle h, char *from, char *to);
int kv_lock(xmldb_handle h, char *db, int pid);

View file

@ -64,7 +64,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean:
rm -f $(PLUGIN) $(OBJS) *.core
@ -80,12 +80,12 @@ distclean: clean
install: $(PLUGIN)
install -d $(DESTDIR)$(clixon_LIBDIR)/xmldb
install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb;
install $(PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/xmldb
install-include:
uninstall:
rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN);
rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -

View file

@ -265,9 +265,52 @@ xml_spec_populate(cxobj *x,
y = yang_find_syntax((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name); /* still NULL for config */
if (y==NULL){
clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'",
name,
xp?xml_name(xp):"", yp?yp->ys_argument:"");
goto done;
}
xml_spec_set(x, y);
retval = 0;
// done:
done:
return retval;
}
/*! Ensure that xt only has a single sub-element and that is "config"
*/
static int
singleconfigroot(cxobj *xt,
cxobj **xp)
{
int retval = -1;
cxobj *x = NULL;
int i = 0;
/* There should only be one element and called config */
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
i++;
if (strcmp(xml_name(x), "config")){
clicon_err(OE_DB, ENOENT, "Wrong top-element %s expected config",
xml_name(x));
goto done;
}
}
if (i != 1){
clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
goto done;
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if (xml_rm(x) < 0)
goto done;
if (xml_free(xt) < 0)
goto done;
*xp = x;
}
retval = 0;
done:
return retval;
}
@ -344,10 +387,15 @@ text_get(xmldb_handle xh,
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
assert(xml_child_nr(xt)==1);
if (xml_rootchild(xt, 0, &xt) < 0)
/* There should only be one element and called config */
if (singleconfigroot(xt, &xt) < 0)
goto done;
}
/* Here xt looks like: <config>...</config> */
/* Validate existing config tree */
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* XXX Maybe the below is general function and should be moved to xmldb? */
if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0)
goto done;
@ -398,271 +446,19 @@ text_get(xmldb_handle xh,
return retval;
}
/*! Check if child with fullmatch exists
* param[in] cvk vector of index keys
*/
static cxobj *
find_keys_vec(cxobj *xt,
char *name,
cvec *cvk,
char **valvec)
{
cxobj *xi = NULL;
int j;
char *keyname;
char *val;
cg_var *cvi;
char *body;
while ((xi = xml_child_each(xt, xi, CX_ELMNT)) != NULL)
if (strcmp(xml_name(xi), name) == 0){
j = 0;
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
val = valvec[j++];
if ((body = xml_find_body(xi, keyname)) == NULL)
break;
if (strcmp(body, val))
break;
}
/* All keys must match: loop terminates. */
if (cvi==NULL)
return xi;
}
return NULL;
}
/*! Create 'modification' tree from api-path, ie fill in xml tree from the path
* @param[in] api_path api-path expression
* @param[in] xt XML tree. Find api-path (or create) in this tree
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] yspec Yang spec
* @param[out] xp Resulting xml tree corresponding to xt
* @param[out] xparp Parent of xp (xp can be NULL)
* @param[out] yp Yang spec matching xp
* @see xmldb_put_xkey for example
*/
static int
text_apipath_modify(char *api_path,
cxobj *xt,
enum operation_type op,
yang_spec *yspec,
cxobj **xp,
cxobj **xparp,
yang_node **yp)
{
int retval = -1;
char **vec = NULL;
int nvec;
int i;
int j;
char *name;
char *restval;
yang_stmt *y = NULL;
yang_stmt *ykey;
cxobj *x = NULL;
cxobj *xpar = NULL;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
char **valvec = NULL;
int nvalvec;
cg_var *cvi;
char *keyname;
char *val2;
x = xt;
xpar = xml_parent(xt);
if (api_path == NULL || *api_path!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", api_path);
goto done;
}
if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (y == NULL) /* top-node */
y = yang_find_topnode(yspec, name);
else
y = yang_find_syntax((yang_node*)y, name);
if (y == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", name);
goto done;
}
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
/* See if it exists */
xn = NULL;
while ((xn = xml_child_each(x, xn, CX_ELMNT)) != NULL)
if (strcmp(name, xml_name(xn)) == 0 &&
strcmp(xml_body(xn),restval)==0)
break;
if (xn == NULL){ /* Not found, does not exist */
switch (op){
case OP_DELETE: /* not here, should be here */
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
break;
case OP_REMOVE:
goto ok; /* not here, no need to remove */
break;
case OP_CREATE:
if (i==nvec) /* Last, dont create here */
break;
default:
//XXX create_keyvalues(cxobj *x,
if ((xn = xml_new_spec(y->ys_argument, x, y)) == NULL)
goto done;
// xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new("body", xn)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, restval) < 0)
goto done;
break;
}
}
xpar = x;
x = xn;
break;
case Y_LIST:
/* Get the yang list key */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if (valvec)
free(valvec);
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Check if exists, if not, create */
if ((xn = find_keys_vec(x, name, cvk, valvec)) == NULL){
/* create them, but not if delete op */
switch (op){
case OP_DELETE: /* not here, should be here */
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
break;
case OP_REMOVE:
goto ok; /* not here, no need to remove */
break;
default:
if ((xn = xml_new(name, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
break;
}
xpar = x;
x = xn;
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
val2 = valvec[j++];
if ((xn = xml_new(keyname, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new("body", xn)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, val2) <0)
goto done;
}
}
else{
xpar = x;
x = xn;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default: /* eg Y_CONTAINER, Y_LEAF */
if ((xn = xml_find(x, name)) == NULL){
switch (op){
case OP_DELETE: /* not here, should be here */
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
break;
case OP_REMOVE:
goto ok; /* not here, no need to remove */
break;
case OP_CREATE:
if (i==nvec) /* Last, dont create here */
break;
default:
if ((xn = xml_new(name, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
break;
}
}
else{
if (op==OP_CREATE && i==nvec){ /* here, should not be here */
clicon_err(OE_XML, 0, "Object to create already exists");
goto done;
}
}
xpar = x;
x = xn;
break;
}
}
*xp = x;
*xparp = xpar;
*yp = (yang_node*)y;
ok:
retval = 0;
done:
if (vec)
free(vec);
if (valvec)
free(valvec);
return retval;
}
/*! Given a modification tree, check existing matching child in the base tree
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child
* param[out] x0cp Matching base tree child (if any)
*/
static cxobj *
static int
match_base_child(cxobj *x0,
cxobj *x1c,
yang_stmt *yc)
yang_stmt *yc,
cxobj **x0cp)
{
int retval = -1;
cxobj *x0c = NULL;
char *keyname;
cvec *cvk = NULL;
@ -719,10 +515,12 @@ match_base_child(cxobj *x0,
x0c = xml_find(x0, cname);
break;
}
*x0cp = x0c;
retval = 0;
done:
if (cvk)
cvec_free(cvk);
return x0c;
return retval;
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
@ -730,165 +528,211 @@ match_base_child(cxobj *x0,
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] y Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] yspec Top-level yang spec (if y is NULL)
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
*/
static int
text_modify(cxobj *x0,
yang_node *y0,
cxobj *x0p,
cxobj *x1,
enum operation_type op,
yang_node *y,
yang_spec *yspec)
enum operation_type op)
{
int retval = -1;
char *opstr;
char *name;
char *cname; /* child name */
char *x1name;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
clicon_debug(1, "%s %s", __FUNCTION__, x0?xml_name(x0):"");
assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0);
/* Check for operations embedded in tree according to netconf */
if (x1 && (opstr = xml_find_value(x1, "operation")) != NULL)
if ((opstr = xml_find_value(x1, "operation")) != NULL)
if (xml_operation(opstr, &op) < 0)
goto done;
if (x1 == NULL){
x1name = xml_name(x1);
if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
x1bstr = xml_body(x1);
switch(op){
case OP_REPLACE:
if (x0)
xml_purge(x0);
case OP_CREATE:
case OP_MERGE:
break;
case OP_CREATE:
if (x0){
clicon_err(OE_XML, 0, "Object to create already exists");
goto done;
}
case OP_NONE: /* fall thru */
case OP_MERGE:
case OP_REPLACE:
if (x0==NULL){
// int iamkey=0;
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done;
#if 0
/* If it is key I dont want to mark it */
if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
goto done;
if (!iamkey && op==OP_NONE)
#else
if (op==OP_NONE)
#endif
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) == NULL){
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
break;
case OP_DELETE:
if (x0==NULL){
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
}
case OP_REMOVE: /* fall thru */
if (x0)
xml_purge(x0);
break;
default:
break;
}
}
else {
assert(xml_type(x1) == CX_ELMNT);
name = xml_name(x1);
if (y && (y->yn_keyword == Y_LEAF_LIST || y->yn_keyword == Y_LEAF)){
x1bstr = xml_body(x1);
switch(op){
case OP_CREATE:
if (x0){
clicon_err(OE_XML, 0, "Object to create already exists");
} /* switch op */
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST */
switch(op){
case OP_CREATE:
if (x0){
clicon_err(OE_XML, 0, "Object to create already exists");
goto done;
}
case OP_REPLACE: /* fall thru */
if (x0){
xml_purge(x0);
x0 = NULL;
}
case OP_NONE: /* fall thru */
case OP_MERGE:
if (x0==NULL){
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done;
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_syntax(y0, x1cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
goto done;
}
/* Fall thru */
case OP_NONE: /* XXX */
case OP_MERGE:
case OP_REPLACE:
if (x0==NULL){
if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
goto done;
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) == NULL){
if ((x0b = xml_new("body", x0)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
break;
case OP_DELETE:
if (x0==NULL){
clicon_err(OE_XML, 0, "Object to delete does not exist");
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
}
case OP_REMOVE:
if (x0)
xml_purge(x0);
break;
default:
break;
} /* switch op */
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER */
switch(op){
case OP_CREATE:
/* top-level object <config/> is a special case, ie when
* x0 parent is NULL
* or x1 is empty
*/
if ((x0p && x0) ||
(x0p==NULL && xml_child_nr(x1) == 0)){
clicon_err(OE_XML, 0, "Object to create already exists");
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
goto done;
}
case OP_REPLACE:
/* top-level object <config/> is a special case, ie when
* x0 parent is NULL,
* or x1 is empty
*/
if ((x0p && x0) ||
(x0p==NULL && xml_child_nr(x1) == 0)){
xml_purge(x0);
x0 = NULL;
}
case OP_NONE: /* XXX */
case OP_MERGE:
if (x0==NULL){
if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
goto done;
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
cname = xml_name(x1c);
/* Get yang spec of the child */
if (y == NULL)
yc = yang_find_topnode(yspec, cname); /* still NULL for config */
else{
if ((yc = yang_find_syntax(y, cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", cname);
goto done;
}
}
/* See if there is a corresponding node in the base tree */
x0c = yc?match_base_child(x0, x1c, yc):NULL;
if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0)
goto done;
}
break;
case OP_DELETE:
if (x0==NULL){
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
}
case OP_REMOVE:
if (x0)
xml_purge(x0);
break;
default:
break;
} /* CONTAINER switch op */
} /* else Y_CONTAINER */
} /* x1 != NULL */
}
break;
case OP_DELETE:
if (x0==NULL){
clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done;
}
case OP_REMOVE: /* fall thru */
if (x0)
xml_purge(x0);
break;
default:
break;
} /* CONTAINER switch op */
} /* else Y_CONTAINER */
// ok:
retval = 0;
done:
return retval;
}
/*! Modify a top-level base tree x0 with modification tree x1
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 xml tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL)
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @see text_modify
*/
static int
text_modify_top(cxobj *x0,
cxobj *x1,
yang_spec *yspec,
enum operation_type op)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc; /* yang child */
/* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0);
assert(x1 && strcmp(xml_name(x1),"config")==0);
/* Special case if x1 is empty, top-level only <config/> */
if (!xml_child_nr(x1)){ /* base tree not empty */
if (xml_child_nr(x0))
switch(op){
case OP_DELETE:
case OP_REMOVE:
case OP_REPLACE:
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL)
xml_purge(x0c);
break;
default:
break;
}
else /* base tree empty */
switch(op){
case OP_DELETE:
clicon_err(OE_XML, 0, "Object to delete does not exist");
break;
default:
break;
}
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_topnode(yspec, x1cname)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Modify database provided an xml tree and an operation
*
* @param[in] xh XMLDB handle
@ -896,9 +740,7 @@ text_modify(cxobj *x0,
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13
])
* @param[in] xadd xml-tree to merge/replace. Top-level symbol is 'config'.
* @param[in] x1 xml-tree to merge/replace. Top-level symbol is 'config'.
* Should be empty or '<config/>' if delete?
* @retval 0 OK
* @retval -1 Error
@ -910,34 +752,21 @@ text_modify(cxobj *x0,
* if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0)
* err;
* @endcode
*/
y */
int
text_put(xmldb_handle xh,
char *db,
enum operation_type op,
char *api_path,
cxobj *xmod)
cxobj *x1)
{
int retval = -1;
struct text_handle *th = handle(xh);
char *dbfile = NULL;
int fd = -1;
cbuf *cb = NULL;
cbuf *xpcb = NULL; /* xpath cbuf */
yang_spec *yspec;
cxobj *xt = NULL;
cxobj *xbase = NULL;
cxobj *xbasep = NULL; /* parent */
cxobj *xc;
cxobj *xnew = NULL;
yang_node *y = NULL;
cxobj *x0 = NULL;
#if 0 /* Just ignore */
if ((op==OP_DELETE || op==OP_REMOVE) && xmod){
clicon_err(OE_XML, 0, "xml tree should be NULL for REMOVE/DELETE");
goto done;
}
#endif
if (text_db2file(th, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
@ -953,56 +782,41 @@ text_put(xmldb_handle xh,
goto done;
}
/* Parse file into XML tree */
if ((clicon_xml_parse_file(fd, &xt, "</config>")) < 0)
if ((clicon_xml_parse_file(fd, &x0, "</config>")) < 0)
goto done;
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */
if (xml_child_nr(xt) == 0){
if (xml_name_set(xt, "config") < 0)
if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0)
goto done;
}
/* 2. File is not empty <top><config>...</config></top> -> replace root */
else{
assert(xml_child_nr(xt)==1);
if (xml_rootchild(xt, 0, &xt) < 0)
/* There should only be one element and called config */
if (singleconfigroot(x0, &x0) < 0)
goto done;
}
/* here xt looks like: <config>...</config> */
/* If xpath find first occurence or api-path (this is where we apply xml) */
if (api_path){
if (text_apipath_modify(api_path, xt, op, yspec, &xbase, &xbasep, &y) < 0)
goto done;
}
else{
xbase = xt; /* defer y since x points to config */
xbasep = xml_parent(xt); /* NULL */
assert(strcmp(xml_name(xbase),"config")==0);
}
assert(strcmp(xml_name(x0),"config")==0);
/* Here x0 looks like: <config>...</config> */
/* Validate existing config tree */
if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Validate modification tree */
if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/*
* Modify base tree x with modification xmod
* Modify base tree x with modification x1
*/
if (op == OP_DELETE || op == OP_REMOVE){
/* special case if top-level, dont purge top-level */
if (xt == xbase){
xc = NULL;
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){
xml_purge(xc);
xc = NULL; /* reset iterator */
}
}
else
if (xbase)
xml_purge(xbase);
}
else
if (text_modify(xbase, xbasep, xmod, op, (yang_node*)y, yspec) < 0)
goto done;
/* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged(xt, XML_FLAG_NONE, 0, NULL) <0)
if (text_modify_top(x0, x1, yspec, op) < 0)
goto done;
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
/* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0)
goto done;
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)XML_FLAG_NONE) < 0)
goto done;
// output:
@ -1011,7 +825,7 @@ text_put(xmldb_handle xh,
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
if (clicon_xml2cbuf(cb, x0, 0, 1) < 0)
goto done;
/* Reopen file in write mode */
close(fd);
@ -1019,7 +833,7 @@ text_put(xmldb_handle xh,
clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
goto done;
}
if (write(fd, cbuf_get(cb), cbuf_len(cb)+1) < 0){
if (write(fd, cbuf_get(cb), cbuf_len(cb)) < 0){
clicon_err(OE_UNIX, errno, "write(%s)", dbfile);
goto done;
}
@ -1031,12 +845,8 @@ text_put(xmldb_handle xh,
close(fd);
if (cb)
cbuf_free(cb);
if (xpcb)
cbuf_free(xpcb);
if (xt)
xml_free(xt);
if (xnew)
xml_free(xnew);
if (x0)
xml_free(x0);
return retval;
}

View file

@ -41,8 +41,7 @@
*/
int text_get(xmldb_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int text_put(xmldb_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt);
int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, char *from, char *to);
int text_lock(xmldb_handle h, char *db, int pid);

View file

@ -4,9 +4,9 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="routing_cli";
# Note, when switching to PT, change datamodel to only @datamodel
set @datamodel:ietf-ip, cli_merge();
#delete("Delete a configuration item") @datamodel:ietf-ipv4-unicast-routing, cli_del();
set @datamodel:ietf-ip, cli_set();
merge @datamodel:ietf-ip, cli_merge();
create @datamodel:ietf-ip, cli_create();
delete("Delete a configuration item") @datamodel:ietf-ip, cli_del();
validate("Validate changes"), cli_validate();

View file

@ -47,7 +47,7 @@ int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret);
int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op,
char *api_path, char *xml);
char *xml);
int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);

View file

@ -45,7 +45,7 @@ enum operation_type{ /* edit-configo */
OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */
OP_DELETE, /* delete config data, error if it does not exist */
OP_REMOVE, /* delete config data */
OP_REMOVE, /* delete config data (not a netconf feature) */
OP_NONE
};

View file

@ -80,7 +80,7 @@ typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath,
/* Type of xmldb put function */
typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op,
char *api_path, cxobj *xt);
cxobj *xt);
/* Type of xmldb copy function */
typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to);
@ -141,8 +141,7 @@ int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt);
int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);
int xmldb_unlock(clicon_handle h, char *db);

View file

@ -61,14 +61,15 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
cxobj ***first, size_t *firstlen,
cxobj ***second, size_t *secondlen,
cxobj ***changed1, cxobj ***changed2, size_t *changedlen);
int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt);
int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk);
int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk);
int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt);
int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path);
int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark);
int xml_default(cxobj *x, void *arg);
int xml_order(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp);
#endif /* _CLIXON_XML_MAP_H_ */

View file

@ -224,7 +224,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/
/* top: json -> value is also possible */
json : value J_EOF { clicon_debug(1,"json->object"); YYACCEPT; }
json : value J_EOF { clicon_debug(2,"json->object"); YYACCEPT; }
;
value : J_TRUE { json_current_body(_JY, "true");}

View file

@ -119,9 +119,12 @@ format_str2int(char *str)
return fv?fv->fv_int:-1;
}
/*! Encode a clicon netconf message
/*! Encode a clicon netconf message using variable argument lists
* @param[in] format Variable agrument list format an XML netconf string
* @retval msg Clicon message to send to eg clicon_msg_send()
* @note if format includes %, they will be expanded according to printf rules.
* if this is a problem, use ("%s", xml) instaead of (xml)
* Notaly this may an issue of RFC 3896 encoded strings
*/
struct clicon_msg *
clicon_msg_encode(char *format, ...)

View file

@ -260,7 +260,7 @@ clicon_rpc_get_config(clicon_handle h,
if (xpath && strlen(xpath))
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
cprintf(cb, "</get-config></rpc>");
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -291,13 +291,12 @@ clicon_rpc_get_config(clicon_handle h,
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
* @param[in] api_path restconf API Path (or "")
* @param[in] xml XML string. Ex: <config><a>..</a><b>...</b></config>
* @retval 0 OK
* @retval -1 Error
* @note xml arg need to have <config> as top element
* @code
* if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/",
* if (clicon_rpc_edit_config(h, "running", OP_MERGE,
* "<config><a>4</a></config>") < 0)
* err;
* @endcode
@ -306,7 +305,6 @@ int
clicon_rpc_edit_config(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
char *xmlstr)
{
int retval = -1;
@ -320,12 +318,10 @@ clicon_rpc_edit_config(clicon_handle h,
cprintf(cb, "<rpc><edit-config><target><%s/></target>", db);
cprintf(cb, "<default-operation>%s</default-operation>",
xml_operation2str(op));
if (api_path && strlen(api_path))
cprintf(cb, "<filter type=\"restconf\" select=\"%s\"/>", api_path);
if (xmlstr)
cprintf(cb, "%s", xmlstr);
cprintf(cb, "</edit-config></rpc>");
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;

View file

@ -374,7 +374,6 @@ xmldb_get(clicon_handle h,
* @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
@ -382,16 +381,14 @@ xmldb_get(clicon_handle h,
* cxobj *xt;
* if (clicon_xml_parse_str("<a>17</a>", &xt) < 0)
* err;
* if (xmldb_put(xh, "running", OP_MERGE, NULL, xt) < 0)
* if (xmldb_put(xh, "running", OP_MERGE, xt) < 0)
* err;
* @endcode
* @see xmldb_put_xkey for single key
*/
int
xmldb_put(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
cxobj *xt)
{
int retval = -1;
@ -417,12 +414,12 @@ xmldb_put(clicon_handle h,
if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
goto done;
clicon_log(LOG_WARNING, "%s: db:%s op:%d api_path:%s xml:%s", __FUNCTION__,
db, op, api_path, cbuf_get(cb));
clicon_log(LOG_WARNING, "%s: db:%s op:%d xml:%s", __FUNCTION__,
db, op, cbuf_get(cb));
cbuf_free(cb);
}
#endif
retval = xa->xa_put_fn(xh, db, op, api_path, xt);
retval = xa->xa_put_fn(xh, db, op, xt);
done:
return retval;
}

View file

@ -121,7 +121,9 @@ tleaf(cxobj *x)
* @param[in] level print 4 spaces per level in front of each line
*/
int
xml2txt(FILE *f, cxobj *x, int level)
xml2txt(FILE *f,
cxobj *x,
int level)
{
cxobj *xe = NULL;
int children=0;
@ -799,13 +801,13 @@ xml_diff(yang_spec *yspec,
* yang: container a -> list b -> key c -> leaf d
* xpath: /a/b/%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex)
* @param[out] cbuf keyfmt
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] cb api_path_fmt,
*/
static int
yang2xmlkeyfmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
yang2api_path_fmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_node *yp; /* parent */
yang_stmt *ykey;
@ -817,7 +819,7 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
if (yp != NULL &&
yp->yn_keyword != Y_MODULE &&
yp->yn_keyword != Y_SUBMODULE){
if (yang2xmlkeyfmt_1((yang_stmt *)yp, 1, cb) < 0)
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0)
goto done;
}
if (inclkey){
@ -825,11 +827,14 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
cprintf(cb, "/%s", ys->ys_argument);
}
else{
if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){
#if 1
if (ys->ys_keyword == Y_LEAF && yp &&
yp->yn_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */
}
else
#endif
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument);
}
@ -866,19 +871,19 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
return retval;
}
/*! Construct an xml key format from yang statement using wildcards for keys
/*! Construct an api_path_format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* xpath: /a/b=%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If !inclkey then dont include key leaf
* @param[out] xkfmt XML key format. Needs to be freed after use.
* api_path: /a/b=%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] api_path_fmt XML api path. Needs to be freed after use.
*/
int
yang2xmlkeyfmt(yang_stmt *ys,
int inclkey,
char **xkfmt)
yang2api_path_fmt(yang_stmt *ys,
int inclkey,
char **api_path_fmt)
{
int retval = -1;
cbuf *cb = NULL;
@ -887,9 +892,9 @@ yang2xmlkeyfmt(yang_stmt *ys,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0)
if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
goto done;
if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){
if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
@ -904,19 +909,20 @@ yang2xmlkeyfmt(yang_stmt *ys,
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
* xmlkeyfmt: /aaa/%s
* xmlkeyfmt: /aaa/%s/name
* cvv: key=17
* xmlkey: /aaa/17
* @param[in] xkfmt XML key format, eg /aaa/%s
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt
* @param[out] xk XML key, eg /aaa/17. Free after use
* xmlkey: /aaa/17/name
* @param[in] api_path_fmt XML key format, eg /aaa/%s/name
* @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] api_path api_path, eg /aaa/17. Free after use
* @param[out] yang_arg yang-stmt argument name. Free after use
* @note first and last elements of cvv are not used,..
* @see cli_dbxml where this function is called
*/
int
xmlkeyfmt2key(char *xkfmt,
cvec *cvv,
char **xk)
api_path_fmt2api_path(char *api_path_fmt,
cvec *cvv,
char **api_path)
{
int retval = -1;
char c;
@ -927,16 +933,15 @@ xmlkeyfmt2key(char *xkfmt,
char *str;
char *strenc=NULL;
/* Sanity check */
#if 1
j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++)
if (xkfmt[i] == '%')
for (i=0; i<strlen(api_path_fmt); i++)
if (api_path_fmt[i] == '%')
j++;
if (j+2 < cvec_len(cvv)) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt,
api_path_fmt,
j,
cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0)));
@ -948,8 +953,8 @@ xmlkeyfmt2key(char *xkfmt,
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){
c = xkfmt[i];
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
@ -967,10 +972,13 @@ xmlkeyfmt2key(char *xkfmt,
else
if (c == '%')
esc++;
else if (c == '/'){
cprintf(cb, "%c", c);
}
else
cprintf(cb, "%c", c);
}
if ((*xk = strdup(cbuf_get(cb))) == NULL){
if ((*api_path = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
@ -993,14 +1001,14 @@ xmlkeyfmt2key(char *xkfmt,
* xmlkeyfmt: /ip/me/%s (if key)
* cvv: -
* xmlkey: /ipv4/me/a
* @param[in] xkfmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt
* @param[out] xk XPATH
* @param[in] api_path_fmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] xpath XPATH
*/
int
xmlkeyfmt2xpath(char *xkfmt,
cvec *cvv,
char **xk)
api_path_fmt2xpath(char *api_path_fmt,
cvec *cvv,
char **xpath)
{
int retval = -1;
char c;
@ -1015,12 +1023,12 @@ xmlkeyfmt2xpath(char *xkfmt,
/* Sanity check: count '%' */
#if 1
j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++)
if (xkfmt[i] == '%')
for (i=0; i<strlen(api_path_fmt); i++)
if (api_path_fmt[i] == '%')
j++;
if (j < cvec_len(cvv)-1) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt,
api_path_fmt,
j,
cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0)));
@ -1032,8 +1040,8 @@ xmlkeyfmt2xpath(char *xkfmt,
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){
c = xkfmt[i];
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
@ -1059,13 +1067,13 @@ xmlkeyfmt2xpath(char *xkfmt,
if (skip)
skip=0;
else
if ((c == '=' || c == ',') && xkfmt[i+1]=='%')
if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*xk = strdup4(cbuf_get(cb))) == NULL){
if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
@ -1094,13 +1102,17 @@ xml_tree_prune_flagged(cxobj *xt,
int test,
int *upmark)
{
int retval = -1;
int submark;
int mark;
cxobj *x;
cxobj *xprev;
int retval = -1;
int submark;
int mark;
cxobj *x;
cxobj *xprev;
int iskey;
int anykey=0;
yang_node *yt;
mark = 0;
yt = xml_spec(xt);
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
@ -1109,8 +1121,20 @@ xml_tree_prune_flagged(cxobj *xt,
xprev = x;
continue; /* mark and stop here */
}
/* If it is key dont remove it yet (see second round) */
if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
goto done;
if (iskey){
anykey++;
xprev = x; /* skip if this is key */
continue;
}
}
if (xml_tree_prune_flagged(x, flag, test, &submark) < 0)
goto done;
/* if xt is list and submark anywhere, then key subs are also marked
*/
if (submark)
mark++;
else{ /* Safe with xml_child_each if last */
@ -1120,6 +1144,22 @@ xml_tree_prune_flagged(cxobj *xt,
}
xprev = x;
}
/* Second round: if any keys were found, and no marks detected, purge now */
if (anykey && !mark){
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
/* If it is key remove it here */
if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
goto done;
if (iskey && xml_purge(x) < 0)
goto done;
x = xprev;
}
xprev = x;
}
}
retval = 0;
done:
if (upmark)
@ -1397,3 +1437,181 @@ api_path2xpath(yang_spec *yspec,
cvec_free(api_path_cvv);
return retval;
}
/*! Create xml tree from api-path as vector
* @param[in] vec APIpath as char* vector
* @param[in] nvec Length of vec
* @param[in] x0 Xpath tree so far
* @param[in] y0 Yang spec for x0
* @param[out] xpathp Resulting xml tree
* @param[out] ypathp Yang spec matching xpathp
* @see api_path2xml
*/
static int
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_node *y0,
cxobj **xpathp,
yang_node **ypathp)
{
int retval = -1;
int j;
char *name;
char *restval = NULL;
char *restval_enc;
yang_stmt *ykey;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
char *val2;
char **valvec = NULL;
int nvalvec;
cxobj *x = NULL;
yang_stmt *y = NULL;
if ((name = vec[0]) == NULL){
*xpathp = x0;
*ypathp = y0;
return 0;
} /* E.g "x=1,2" -> name:x restval=1,2 */
/* restval is RFC 3896 encoded */
if ((restval_enc = index(name, '=')) != NULL){
*restval_enc = '\0';
restval_enc++;
if (percent_decode(restval_enc, &restval) < 0)
goto done;
}
if (y0->yn_keyword == Y_SPEC) /* top-node */
y = yang_find_topnode((yang_spec*)y0, name);
else
y = yang_find_syntax((yang_node*)y0, name);
if (y == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", name);
goto done;
}
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if ((x = xml_new_spec(y->ys_argument, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
if ((xb = xml_new("body", x)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, restval) < 0)
goto done;
break;
case Y_LIST:
/* Get the yang list key */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if (valvec)
free(valvec);
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* create list object */
if ((x = xml_new_spec(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
j = 0;
/* Create keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
val2 = valvec[j++];
if ((xn = xml_new(keyname, x)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new("body", xn)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (xml_value_set(xb, val2) <0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default: /* eg Y_CONTAINER, Y_LEAF */
if ((x = xml_new_spec(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
break;
}
if (api_path2xml_vec(vec+1, nvec-1,
x, (yang_node*)y,
xpathp, ypathp) < 0)
goto done;
retval = 0;
done:
if (restval)
free(restval);
if (valvec)
free(valvec);
return retval;
}
/*! Create xml tree from api-path
* @param[in] api_path API-path as defined in RFC 8040
* @param[out] xpathp Resulting xml tree
* @param[out] ypathp Yang spec matching xpathp
* @see api_path2xml_vec
*/
int
api_path2xml(char *api_path,
yang_spec *yspec,
cxobj *xpath,
cxobj **xpathp,
yang_node **ypathp)
{
int retval = -1;
char **vec = NULL;
int nvec;
clicon_debug(1, "%s 0", __FUNCTION__);
if (*api_path!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", api_path);
goto done;
}
if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
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,
xpathp, ypathp) < 0)
goto done;
retval = 0;
done:
if (vec)
free(vec);
return retval;
}

View file

@ -79,23 +79,6 @@ clixon_xml_parseerror(void *_ya, char *s)
return;
}
static int
xml_attr_new(struct xml_parse_yacc_arg *ya,
cxobj *xn,
char *name,
char *val)
{
cxobj *xa;
if ((xa = xml_new(name, xn)) == NULL)
return -1;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, val) < 0)
return -1;
return 0;
}
/* note that we dont handle escaped characters correctly
there may also be some leakage here on NULL return
*/
@ -194,12 +177,16 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
xml_namespace(x), xml_name(x), name);
goto done;
}
/* remove all non-terminal bodies (strip pretty-print) */
/* Strip pretty-print. Ad-hoc algorithm
* It ok with x:[body], but not with x:[ex,body]
* It is also ok with x:[attr,body]
* So the rule is: if there is at least on element, then remove all bodies?
*/
if (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
;
else{
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
break;
if (xc != NULL){ /* at least one element */
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_purge(xc);
@ -240,14 +227,20 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
name);
goto done;
}
/* remove all non-terminal bodies (strip pretty-print) */
/* Strip pretty-print. Ad-hoc algorithm
* It ok with x:[body], but not with x:[ex,body]
* It is also ok with x:[attr,body]
* So the rule is: if there is at least on element, then remove all bodies?
*/
if (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
;
else{
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
break;
if (xc != NULL){ /* at least one element */
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL)
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_value_set(xc, ""); /* XXX remove */
}
}
}
retval = 0;
@ -276,8 +269,12 @@ static int
xml_parse_attr(struct xml_parse_yacc_arg *ya, char *id, char *val)
{
int retval = -1;
cxobj *xa;
if (xml_attr_new(ya, ya->ya_xelement, id, val) < 0)
if ((xa = xml_new(id, ya->ya_xelement)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, val) < 0)
goto done;
retval = 0;
done:

View file

@ -21,6 +21,11 @@ new(){
expectfn(){
cmd=$1
expect=$2
if [ $# = 3 ]; then
expect2=$3
else
expect2=
fi
ret=`$cmd`
if [ $? -ne 0 ]; then
err
@ -30,12 +35,18 @@ expectfn(){
return
fi
# grep extended grep
match=`echo "$ret" | grep -Eo "$expect"`
# echo "ret:<$ret>"
# echo "expect:$expect"
# echo "match:$match"
match=`echo "$ret" | grep -EZo "$expect"`
# echo "ret:\"$ret\""
# echo "expect:\"$expect\""
# echo "match:\"$match\""
if [ -z "$match" ]; then
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
err "Expected:\"$expect\" Got:\"$ret\""
fi
if [ -n "$expect2" ]; then
match=`echo "$ret" | grep -EZo "$expect2"`
if [ -z "$match" ]; then
err "Expected:\"$expect\" Got: \"$ret\""
fi
fi
}
@ -56,8 +67,11 @@ EOF
return
fi
match=`echo "$ret" | grep -Eo "$expect"`
# echo "ret:\"$ret\""
# echo "expect:\"$expect\""
# echo "match:\"$match\""
if [ -z "$match" ]; then
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
err "Expected:\"$expect\" Got: \"$ret\""
fi
}

View file

@ -41,25 +41,24 @@ new "cli show configuration delete top"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" ""
new "cli configure"
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0" ""
new "cli show configuration"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth0
interfaces interface enabled true$"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$"
new "cli failed validate"
expectfn "$clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable"
new "cli configure more"
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 description mydesc" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 description mydesc" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth/0/0 type bgp" ""
new "cli show xpath description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "<description>mydesc</description>"
new "cli delete description"
expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc"
expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth/0/0 description mydesc"
new "cli show xpath no description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" ""
@ -80,8 +79,7 @@ new "cli load"
expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" ""
new "cli check load"
expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth0
interfaces interface enabled true$"
expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$"
new "cli debug"
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" ""

View file

@ -26,38 +26,38 @@ new "netconf tests"
new "netconf get empty config"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "Add subtree eth0 using none which should not change anything"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><default-operation>none</default-operation><target><candidate/></target><config><interfaces><interface><name>eth0</name></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Add subtree eth/0/0 using none which should not change anything"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><default-operation>none</default-operation><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check nothing added"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
new "Add subtree eth0 using none and create which should add eth0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Add subtree eth/0/0 using none and create which should add eth/0/0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check eth0 added using xpath"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth0</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "Check eth/0/0 added using xpath"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth/0/0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth/0/0</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "Re-create same eth0 which should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "Re-create same eth/0/0 which should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "Delete eth0 using none config"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Delete eth/0/0 using none config"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check deleted eth0"
new "Check deleted eth/0/0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><config><interfaces/></config></data></rpc-reply>]]>]]>$'
new "Re-Delete eth0 using none should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "Re-Delete eth/0/0 using none should generate error"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "netconf edit config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf get config xpath parent"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"

View file

@ -27,43 +27,60 @@ sleep 1
new "restconf tests"
new "restconf options"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf head"
expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
expectfn "curl -sS -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
new "restconf get empty config"
expectfn "curl -sG http://localhost/restconf/data" "^null $"
expectfn "curl -sSG http://localhost/restconf/data" "^null $"
#
new "Add subtree eth0,eth1 using POST"
expectfn 'curl -sX POST -d {"interfaces":{"interface":[{"name":"eth0","type":"eth","enabled":"true"},{"name":"eth1","type":"eth","enabled":"true"}]}} http://localhost/restconf/data' ""
new "Add subtree to datastore using POST"
expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
new "Check eth0 added"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth0","type": "eth","enabled": "true"},{ "name": "eth1","type": "eth","enabled": "true"}\]}}
new "Check interfaces eth/0/0 added"
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$'
new "Re-post eth0 which should generate error"
expectfn 'curl -sX POST -d {"interfaces":{"interface":{"name":"eth0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "Not Found"
new "delete interfaces"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
new "delete eth0"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' ""
new "Check empty config"
expectfn "curl -sSG http://localhost/restconf/data" "^null $"
new "Check deleted eth0"
expectfn 'curl -sG http://localhost/restconf/data' '{"interfaces": {"interface": {"name": "eth1","type": "eth","enabled": "true"}}}
new "Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' ""
new "Check eth/0/0 added"
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$'
new "Re-Delete eth0 using none should generate error"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "Not Found"
new "Re-post eth/0/0 which should generate error"
expectfn 'curl -sS -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces' "Data resource already exists"
if false; then # XXX restconf dont support patch and put fully
new "Add leaf description using POST"
expectfn 'curl -sS -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new "restconf PATCH config"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "Check description added"
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": "true"}}
$'
new "restconf PUT"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' ""
fi
new "delete eth/0/0"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new "Check deleted eth/0/0"
expectfn 'curl -sS -G http://localhost/restconf/data' '{"interfaces": null}
$'
new "Re-Delete eth/0/0 using none should generate error"
expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found"
new "Add subtree eth/0/0 using PUT"
expectfn 'curl -sS -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
expectfn "curl -sS -G http://localhost/restconf/data" '{"interfaces": {"interface": {"name": "eth/0/0","type": "eth","enabled": "true"}}}
$'
new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf

View file

@ -60,37 +60,37 @@ run(){
# Whole tree operations
new "datastore $name put all replace"
expectfn "$datastore $conf put replace / $db" ""
expectfn "$datastore $conf put replace $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all remove"
expectfn "$datastore $conf put remove /"
expectfn "$datastore $conf put remove <config/>"
new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all merge"
expectfn "$datastore $conf put merge / $db" ""
expectfn "$datastore $conf put merge $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all delete"
expectfn "$datastore $conf put remove /"
expectfn "$datastore $conf put remove <config/>"
new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all create"
expectfn "$datastore $conf put create / $db" ""
expectfn "$datastore $conf put create $db" ""
new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put top create"
expectfn "$datastore $conf put create / <config><x/></config>" "" # error
expectfn "$datastore $conf put create <config><x/></config>" "" # error
# Single key operations
# leaf
@ -101,43 +101,43 @@ run(){
expectfn "$datastore $conf init" ""
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put create <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put create <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore $name delete leaf"
expectfn "$datastore $conf put delete /x/y=1,3"
expectfn "$datastore $conf put delete <config><x><y><a>1</a><b>3</b></y></x></config>"
new "datastore $name replace leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put create <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore $name remove leaf"
expectfn "$datastore $conf put remove /x/g"
expectfn "$datastore $conf put remove <config><x><g/></x></config>"
new "datastore $name remove leaf"
expectfn "$datastore $conf put remove /x/y=1,3/c"
expectfn "$datastore $conf put remove <config><x><y><a>1</a><b>3</b><c/></y></x></config>"
new "datastore $name delete leaf"
expectfn "$datastore $conf put delete /x/g"
expectfn "$datastore $conf put delete <config><x><g/></x></config>"
new "datastore $name merge leaf"
expectfn "$datastore $conf put merge /x/g <g>nalle</g>"
expectfn "$datastore $conf put merge <config><x><g>nalle</g></x></config>"
new "datastore $name replace leaf"
expectfn "$datastore $conf put replace /x/g <g>nalle</g>"
expectfn "$datastore $conf put replace <config><x><g>nalle</g></x></config>"
new "datastore $name merge leaf"
expectfn "$datastore $conf put merge /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put merge <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore $name replace leaf"
expectfn "$datastore $conf put replace /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put replace <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/h <h><j>aaa</j></h>"
expectfn "$datastore $conf put create <config><x><h><j>aaa</j></h></x></config>"
new "datastore $name create leaf"
expectfn "$datastore $conf put create /x/y=1,3/c <c>newentry</c>"
expectfn "$datastore $conf put create <config><x><y><a>1</a><b>3</b><c>newentry</c></y></x></config>"
#leaf-list