merged develop to 3.3.1
This commit is contained in:
commit
6ebfc182f3
40 changed files with 1205 additions and 1334 deletions
|
|
@ -1,4 +1,10 @@
|
|||
# Clixon CHANGELOG
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Fixed yang leafref cli completion.
|
||||
|
||||
- Removed non-standard api_path extension from internal netconf so that the internal com.
|
||||
|
||||
- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ generated CLI and configuration interface.
|
|||
|
||||
Dependencies
|
||||
============
|
||||
Clixon is dependend on the following software packages, which need to exist on the target machine.
|
||||
Clixon depends on the following software packages, which need to exist on the target machine.
|
||||
- [CLIgen](http://www.cligen.se) is required for building Clixon. If you need
|
||||
to build and install CLIgen:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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>"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ static int
|
|||
backend_plugin_unload(clicon_handle h,
|
||||
struct plugin *plg)
|
||||
{
|
||||
int retval=-1;
|
||||
char *error;
|
||||
|
||||
/* Call exit function is it exists */
|
||||
|
|
@ -165,12 +166,14 @@ backend_plugin_unload(clicon_handle h,
|
|||
if (dlclose(plg->p_handle) != 0) {
|
||||
error = (char*)dlerror();
|
||||
clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error");
|
||||
return -1;
|
||||
goto done;
|
||||
/* Just report */
|
||||
}
|
||||
else
|
||||
clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name);
|
||||
return 0;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -357,6 +360,7 @@ backend_plugin_load_dir(clicon_handle h,
|
|||
goto quit;
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
free(new);
|
||||
}
|
||||
|
||||
/* Now load the rest. Note plugins is the global variable */
|
||||
|
|
@ -371,6 +375,7 @@ backend_plugin_load_dir(clicon_handle h,
|
|||
/* Append to 'plugins' */
|
||||
if (plugin_append(new) < 0)
|
||||
goto quit;
|
||||
free(new);
|
||||
}
|
||||
|
||||
/* All good. */
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -276,9 +275,24 @@ yang2cli_var_sub(clicon_handle h,
|
|||
if (helptext)
|
||||
cprintf(cb0, "(\"%s\")", helptext);
|
||||
if (completion){
|
||||
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
||||
options, fraction_digits) < 0)
|
||||
goto done;
|
||||
if (type && (strcmp(type, "leafref") == 0)){
|
||||
yang_stmt *ypath;
|
||||
|
||||
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||
clicon_err(OE_XML, 0, "leafref should have path sub");
|
||||
goto done;
|
||||
}
|
||||
clicon_debug(1, "%s ypath:%s\n", __FUNCTION__, ypath->ys_argument);
|
||||
cprintf(cb0, "|<%s:%s", ys->ys_argument,
|
||||
cv_type2str(cvtype));
|
||||
cprintf(cb0, " %s(\"candidate\",\"%s\")>",
|
||||
GENERATE_EXPAND_XMLDB,
|
||||
ypath->ys_argument);
|
||||
}
|
||||
else
|
||||
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
||||
options, fraction_digits) < 0)
|
||||
goto done;
|
||||
if (helptext)
|
||||
cprintf(cb0, "(\"%s\")", helptext);
|
||||
cprintf(cb0, ")");
|
||||
|
|
@ -403,7 +417,6 @@ yang2cli_leaf(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
yang2cli_container(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,90 @@ 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;
|
||||
}
|
||||
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 +400,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 = NULL;
|
||||
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 +505,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 +527,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 +570,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;
|
||||
}
|
||||
|
|
|
|||
4
configure
vendored
4
configure
vendored
|
|
@ -2172,8 +2172,8 @@ _ACEOF
|
|||
|
||||
# Bind to specific CLIgen version
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}_PRE" >&5
|
||||
$as_echo "CLIXON version is ${CLIXON_VERSION}_PRE" >&6; }
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}" >&5
|
||||
$as_echo "CLIXON version is ${CLIXON_VERSION}" >&6; }
|
||||
|
||||
ac_aux_dir=
|
||||
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ AC_SUBST(CLIXON_VERSION_MAJOR)
|
|||
AC_SUBST(CLIXON_VERSION_MINOR)
|
||||
AC_SUBST(CLIGEN_VERSION) # Bind to specific CLIgen version
|
||||
|
||||
AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION}_PRE)
|
||||
AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION})
|
||||
|
||||
AC_CANONICAL_TARGET
|
||||
AC_SUBST(CC)
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -615,7 +615,6 @@ kv_get(xmldb_handle xh,
|
|||
struct db_pair *pairs;
|
||||
cxobj *xt = NULL;
|
||||
|
||||
|
||||
clicon_debug(2, "%s", __FUNCTION__);
|
||||
if (kv_db2file(kh, db, &dbfile) < 0)
|
||||
goto done;
|
||||
|
|
@ -761,8 +760,39 @@ put(char *dbfile,
|
|||
goto done;
|
||||
}
|
||||
case OP_REMOVE:
|
||||
if (db_del(dbfile, xk) < 0)
|
||||
switch (ys->ys_keyword){
|
||||
case Y_LIST:
|
||||
case Y_CONTAINER:{
|
||||
struct db_pair *pairs;
|
||||
int npairs;
|
||||
cbuf *cbrx;
|
||||
int i;
|
||||
|
||||
if ((cbrx = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbrx, "^%s.*$", xk);
|
||||
if ((npairs = db_regexp(dbfile, cbuf_get(cbrx), __FUNCTION__,
|
||||
&pairs, 0)) < 0)
|
||||
goto done;
|
||||
/* Translate to complete xml tree */
|
||||
for (i = 0; i < npairs; i++)
|
||||
if (db_del(dbfile, pairs[i].dp_key) < 0)
|
||||
goto done;
|
||||
if (cbrx)
|
||||
cbuf_free(cbrx);
|
||||
/* Skip recursion, we have deleted whole subtree */
|
||||
retval = 0;
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (db_del(dbfile, xk) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case OP_NONE:
|
||||
break;
|
||||
|
|
@ -782,497 +812,10 @@ put(char *dbfile,
|
|||
cbuf_free(cbxk);
|
||||
if (bodyenc)
|
||||
free(bodyenc);
|
||||
unchunk_group(__FUNCTION__);
|
||||
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 +842,6 @@ int
|
|||
kv_put(xmldb_handle xh,
|
||||
char *db,
|
||||
enum operation_type op,
|
||||
char *api_path,
|
||||
cxobj *xt)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -1309,9 +851,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 +863,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:
|
||||
|
|
@ -1401,7 +931,6 @@ kv_lock(xmldb_handle xh,
|
|||
{
|
||||
int retval = -1;
|
||||
// struct kv_handle *kh = handle(xh);
|
||||
fprintf(stderr, "%s %s %d\n", __FUNCTION__, db, pid);
|
||||
if (strcmp("running", db) == 0)
|
||||
_running_locked = pid;
|
||||
else if (strcmp("candidate", db) == 0)
|
||||
|
|
@ -1413,8 +942,6 @@ kv_lock(xmldb_handle xh,
|
|||
goto done;
|
||||
}
|
||||
clicon_debug(1, "%s: locked by %u", db, pid);
|
||||
fprintf(stderr, "running:%d candidate:%d startup:%d\n",
|
||||
_running_locked, _candidate_locked, _startup_locked);
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -1434,7 +961,6 @@ kv_unlock(xmldb_handle xh,
|
|||
{
|
||||
int retval = -1;
|
||||
// struct kv_handle *kh = handle(xh);
|
||||
fprintf(stderr, "%s %s\n", __FUNCTION__, db);
|
||||
if (strcmp("running", db) == 0)
|
||||
_running_locked = 0;
|
||||
else if (strcmp("candidate", db) == 0)
|
||||
|
|
@ -1485,9 +1011,6 @@ kv_islocked(xmldb_handle xh,
|
|||
int retval = -1;
|
||||
// struct kv_handle *kh = handle(xh);
|
||||
|
||||
fprintf(stderr, "%s %s\n", __FUNCTION__, db);
|
||||
fprintf(stderr, "running:%d candidate:%d startup:%d\n",
|
||||
_running_locked, _candidate_locked, _startup_locked);
|
||||
if (strcmp("running", db) == 0)
|
||||
retval = _running_locked;
|
||||
else if (strcmp("candidate", db) == 0)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ singleconfigroot(cxobj *xt,
|
|||
if (xml_free(xt) < 0)
|
||||
goto done;
|
||||
*xp = x;
|
||||
break;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -446,281 +447,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_enc;
|
||||
char *restval = NULL;
|
||||
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 */
|
||||
/* restval is RFC 3896 encoded */
|
||||
if (restval){
|
||||
free(restval);
|
||||
restval = NULL;
|
||||
}
|
||||
if ((restval_enc = index(name, '=')) != NULL){
|
||||
*restval_enc = '\0';
|
||||
restval_enc++;
|
||||
if (percent_decode(restval_enc, &restval) < 0)
|
||||
goto done;
|
||||
}
|
||||
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 (restval)
|
||||
free(restval);
|
||||
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;
|
||||
|
|
@ -777,10 +516,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
|
||||
|
|
@ -788,165 +529,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
|
||||
|
|
@ -954,9 +741,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
|
||||
|
|
@ -968,40 +753,32 @@ 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){
|
||||
clicon_err(OE_XML, 0, "dbfile NULL");
|
||||
goto done;
|
||||
}
|
||||
if (x1 && strcmp(xml_name(x1),"config")!=0){
|
||||
clicon_err(OE_XML, 0, "Top-level symbol of modification tree is %s, expected \"config\"",
|
||||
xml_name(x1));
|
||||
goto done;
|
||||
}
|
||||
if ((yspec = th->th_yangspec) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
|
|
@ -1011,59 +788,47 @@ 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{
|
||||
/* The should only be one element and called config */
|
||||
if (singleconfigroot(xt, &xt) < 0)
|
||||
|
||||
/* There should only be one element and called config */
|
||||
if (singleconfigroot(x0, &x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
/* Validate existing config tree */
|
||||
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
/* Here x0 looks like: <config>...</config> */
|
||||
if (strcmp(xml_name(x0),"config")!=0){
|
||||
clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
|
||||
xml_name(x0));
|
||||
goto done;
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* 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:
|
||||
|
|
@ -1072,7 +837,7 @@ text_put(xmldb_handle xh,
|
|||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_xml2cbuf(cb, xt, 0, 1) < 0)
|
||||
if (clicon_xml2cbuf(cb, x0, 0, 1) < 0)
|
||||
goto done;
|
||||
/* Reopen file in write mode */
|
||||
close(fd);
|
||||
|
|
@ -1092,12 +857,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;
|
||||
}
|
||||
|
||||
|
|
@ -1275,6 +1036,8 @@ text_delete(xmldb_handle xh,
|
|||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (filename)
|
||||
free(filename);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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");}
|
||||
|
|
@ -259,6 +259,7 @@ valuelist : value
|
|||
|
||||
/* quoted string */
|
||||
string : J_DQ ustring J_DQ { clicon_debug(2,"string->\" ustring \"");$$=$2; }
|
||||
| J_DQ J_DQ { clicon_debug(2,"string->\" ustring \"");$$=strdup(""); }
|
||||
;
|
||||
|
||||
/* unquoted string */
|
||||
|
|
|
|||
|
|
@ -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, ...)
|
||||
|
|
|
|||
|
|
@ -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,8 +318,6 @@ 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>");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
#if 0
|
||||
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;
|
||||
|
|
@ -1013,14 +1021,14 @@ xmlkeyfmt2xpath(char *xkfmt,
|
|||
int skip = 0;
|
||||
|
||||
/* Sanity check: count '%' */
|
||||
#if 1
|
||||
#if 0
|
||||
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,183 @@ 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){
|
||||
if (xpathp)
|
||||
*xpathp = x0;
|
||||
if (ypathp)
|
||||
*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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -185,26 +168,30 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
|
|||
cxobj *xc;
|
||||
|
||||
if (strcmp(xml_name(x), name)){
|
||||
clicon_err(OE_XML, 0, "Sanity check failed: %s vs %s",
|
||||
clicon_err(OE_XML, 0, "XML parse sanity check failed: %s vs %s",
|
||||
xml_name(x), name);
|
||||
goto done;
|
||||
}
|
||||
if (xml_namespace(x)!=NULL){
|
||||
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s\n",
|
||||
clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s\n",
|
||||
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);
|
||||
xc = NULL; /* reset iterator */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
126
lib/src/json_xpath.c
Normal file
126
lib/src/json_xpath.c
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Utility debug tool
|
||||
* Static Compile: gcc -Wall json_xpath.c -o json_xpath -l clixon
|
||||
gcc -O2 -o json_xpath json_xpath.c clixon_log.o clixon_err.o clixon_json.o clixon_xml.o clixon_xsl.o clixon_json_parse.tab.o clixon_xml_parse.tab.o lex.clixon_json_parse.o lex.clixon_xml_parse.o ../../../cligen/cligen_buf.o ../../../cligen/cligen_var.o ../../../cligen/cligen_gen.o ../../../cligen/cligen_cvec.o ../../../cligen/cligen_handle.o ../../../cligen/getline.o ../../../cligen/cligen_read.o ../../../cligen/cligen_match.o ../../../cligen/cligen_expand.o ../../../cligen/cligen_print.o
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:%s <xpath> # XML/JSON expected on stdin\n"
|
||||
"\t-h Help\n"
|
||||
"\t-b Strip to body value (eg \"<a>val</a>\" --> \"val\"\n"
|
||||
"\t-j json input (not xml)\n",
|
||||
argv0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
cxobj **xv;
|
||||
cxobj *x;
|
||||
cxobj *xn;
|
||||
cxobj *xb;
|
||||
char *xpath;
|
||||
int body = 0;
|
||||
int json = 0;
|
||||
size_t xlen = 0;
|
||||
size_t len;
|
||||
size_t buflen = 128;
|
||||
char *buf;
|
||||
char *p;
|
||||
int retval;
|
||||
char c;
|
||||
|
||||
while ((c = getopt(argc, argv, "?hbj")) != -1)
|
||||
switch (c) {
|
||||
case '?':
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'b':
|
||||
body++;
|
||||
break;
|
||||
case 'j':
|
||||
json++;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
}
|
||||
if (optind >= argc)
|
||||
usage(argv[0]);
|
||||
xpath=argv[optind];
|
||||
|
||||
clicon_log_init("xpath", 0, CLICON_LOG_STDERR);
|
||||
if ((buf = malloc(buflen)) == NULL){
|
||||
perror("malloc");
|
||||
return -1;
|
||||
}
|
||||
/* |---------------|-------------|
|
||||
* buf p buf+buflen
|
||||
*/
|
||||
p = buf;
|
||||
memset(p, 0, buflen);
|
||||
while (1){
|
||||
if ((retval = read(0, p, buflen-(p-buf))) < 0){
|
||||
perror("read");
|
||||
return -1;
|
||||
}
|
||||
if (retval == 0)
|
||||
break;
|
||||
p += retval;
|
||||
len = p-buf;
|
||||
|
||||
if (buf+buflen-p < 10){ /* allocate more */
|
||||
buflen *= 2;
|
||||
if ((buf = realloc(buf, buflen)) == NULL){
|
||||
perror("realloc");
|
||||
return -1;
|
||||
}
|
||||
p = buf+len;
|
||||
memset(p, 0, (buf+buflen)-p);
|
||||
}
|
||||
}
|
||||
if (json){
|
||||
if (json_parse_str(buf, &x) < 0)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
if (clicon_xml_parse_str(buf, &x) < 0)
|
||||
return -1;
|
||||
|
||||
if (xpath_vec(x, xpath, &xv, &xlen) < 0)
|
||||
return -1;
|
||||
if (xv){
|
||||
for (i=0; i<xlen; i++){
|
||||
xn = xv[i];
|
||||
if (body)
|
||||
xb = xml_find(xn, "body");
|
||||
else
|
||||
xb = xn;
|
||||
if (xb){
|
||||
// xml2json(stdout, xb, 0);
|
||||
clicon_xml2file(stdout, xb, 0, 0);
|
||||
fprintf(stdout,"\n");
|
||||
}
|
||||
}
|
||||
free(xv);
|
||||
}
|
||||
if (x)
|
||||
xml_free(x);
|
||||
if (buf)
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
34
test/lib.sh
34
test/lib.sh
|
|
@ -5,7 +5,9 @@ testnname=
|
|||
clixon_cf=/usr/local/etc/routing.conf
|
||||
# error and exit, arg is optional extra errmsg
|
||||
err(){
|
||||
echo "Error in Test$testnr [$testname] $1"
|
||||
echo "Error in Test$testnr [$testname]:"
|
||||
echo "Expected: $1"
|
||||
echo "Received: $2"
|
||||
exit $testnr
|
||||
}
|
||||
|
||||
|
|
@ -21,21 +23,32 @@ new(){
|
|||
expectfn(){
|
||||
cmd=$1
|
||||
expect=$2
|
||||
if [ $# = 3 ]; then
|
||||
expect2=$3
|
||||
else
|
||||
expect2=
|
||||
fi
|
||||
ret=`$cmd`
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
err "wrong args"
|
||||
fi
|
||||
# Match if both are empty string
|
||||
if [ -z "$ret" -a -z "$expect" ]; then
|
||||
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 $expect "$ret"
|
||||
fi
|
||||
if [ -n "$expect2" ]; then
|
||||
match=`echo "$ret" | grep -EZo "$expect2"`
|
||||
if [ -z "$match" ]; then
|
||||
err $expect "$ret"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +69,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 "$expect" "$ret"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +90,7 @@ expectwait(){
|
|||
read ret
|
||||
match=$(echo "$ret" | grep -Eo "$expect");
|
||||
if [ -z "$match" ]; then
|
||||
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
|
||||
err $expect "$ret"
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
|
|
|||
|
|
@ -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" ""
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
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 "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>"
|
||||
|
|
|
|||
|
|
@ -27,46 +27,63 @@ 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
|
||||
sudo pkill -u www-data clixon_restconf
|
||||
|
||||
new "Kill backend"
|
||||
# Check if still alive
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue