merged develop to 3.3.1

This commit is contained in:
Olof hagsand 2017-06-07 20:54:11 +02:00
commit 6ebfc182f3
40 changed files with 1205 additions and 1334 deletions

View file

@ -1,5 +1,11 @@
# Clixon CHANGELOG # 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 - Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
## 3.3.0 ## 3.3.0

View file

@ -39,7 +39,7 @@ generated CLI and configuration interface.
Dependencies 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 - [CLIgen](http://www.cligen.se) is required for building Clixon. If you need
to build and install CLIgen: to build and install CLIgen:
``` ```

View file

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

View file

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

View file

@ -155,6 +155,7 @@ static int
backend_plugin_unload(clicon_handle h, backend_plugin_unload(clicon_handle h,
struct plugin *plg) struct plugin *plg)
{ {
int retval=-1;
char *error; char *error;
/* Call exit function is it exists */ /* Call exit function is it exists */
@ -165,12 +166,14 @@ backend_plugin_unload(clicon_handle h,
if (dlclose(plg->p_handle) != 0) { if (dlclose(plg->p_handle) != 0) {
error = (char*)dlerror(); error = (char*)dlerror();
clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error"); clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error");
return -1; goto done;
/* Just report */ /* Just report */
} }
else else
clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name); 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; goto quit;
if (plugin_append(new) < 0) if (plugin_append(new) < 0)
goto quit; goto quit;
free(new);
} }
/* Now load the rest. Note plugins is the global variable */ /* Now load the rest. Note plugins is the global variable */
@ -371,6 +375,7 @@ backend_plugin_load_dir(clicon_handle h,
/* Append to 'plugins' */ /* Append to 'plugins' */
if (plugin_append(new) < 0) if (plugin_append(new) < 0)
goto quit; goto quit;
free(new);
} }
/* All good. */ /* All good. */

View file

@ -207,21 +207,43 @@ cli_dbxml(clicon_handle h,
{ {
int retval = -1; int retval = -1;
char *str = NULL; char *str = NULL;
char *xkfmt; /* xml key format */ char *api_path_fmt; /* xml key format */
char *xk = NULL; /* xml key */ char *api_path = NULL; /* xml key */
cg_var *cval; cg_var *cval;
int len; int len;
cg_var *arg; cg_var *arg;
cbuf *cb = NULL; 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){ if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__); clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
goto done; goto done;
} }
arg = cvec_i(argv, 0); if ((yspec = clicon_dbspec_yang(h)) == NULL){
xkfmt = cv_string_get(arg); clicon_err(OE_FATAL, 0, "No DB_SPEC");
if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
goto done; 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); len = cvec_len(cvv);
if (len > 1){ if (len > 1){
cval = cvec_i(cvv, len-1); cval = cvec_i(cvv, len-1);
@ -229,16 +251,20 @@ cli_dbxml(clicon_handle h,
clicon_err(OE_UNIX, errno, "cv2str_dup"); clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done; 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){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
if (str) if (clicon_xml2cbuf(cb, xtop, 0, 0) < 0)
cprintf(cb, "<config>%s</config>", str); goto done;
else if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
cprintf(cb, "<config/>");
if (clicon_rpc_edit_config(h, "candidate", op, xk, cbuf_get(cb)) < 0)
goto done; goto done;
if (clicon_autocommit(h)) { if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h) < 0) if (clicon_rpc_commit(h) < 0)
@ -250,8 +276,10 @@ cli_dbxml(clicon_handle h,
cbuf_free(cb); cbuf_free(cb);
if (str) if (str)
free(str); free(str);
if (xk) if (api_path)
free(xk); free(api_path);
if (xtop)
xml_free(xtop);
return retval; return retval;
} }
@ -287,6 +315,31 @@ int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
return cli_merge(h, vars, 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 int
cli_del(clicon_handle h, cvec *cvv, cvec *argv) 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", if (clicon_rpc_edit_config(h, "candidate",
replace?OP_REPLACE:OP_MERGE, replace?OP_REPLACE:OP_MERGE,
"",
cbuf_get(cbxml)) < 0) cbuf_get(cbxml)) < 0)
goto done; goto done;
cbuf_free(cbxml); cbuf_free(cbxml);
@ -1135,7 +1187,7 @@ cli_copy_config(clicon_handle h,
cbuf_reset(cb); cbuf_reset(cb);
/* create xml copy tree and merge it with database configuration */ /* create xml copy tree and merge it with database configuration */
clicon_xml2cbuf(cb, x2, 0, 0); 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; goto done;
retval = 0; retval = 0;
done: done:

View file

@ -64,7 +64,7 @@
#include "cli_generate.h" #include "cli_generate.h"
/* This is the default callback function. But this is typically overwritten */ /* This is the default callback function. But this is typically overwritten */
#define GENERATE_CALLBACK "cli_set" #define GENERATE_CALLBACK "overwrite_me"
/* variable expand function */ /* variable expand function */
#define GENERATE_EXPAND_XMLDB "expand_dbvar" #define GENERATE_EXPAND_XMLDB "expand_dbvar"
@ -115,13 +115,12 @@ cli_expand_var_generate(clicon_handle h,
enum cv_type cvtype, enum cv_type cvtype,
cbuf *cb0, cbuf *cb0,
int options, int options,
uint8_t fraction_digits uint8_t fraction_digits)
)
{ {
int retval = -1; 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; goto done;
cprintf(cb0, "|<%s:%s", ys->ys_argument, cprintf(cb0, "|<%s:%s", ys->ys_argument,
cv_type2str(cvtype)); cv_type2str(cvtype));
@ -129,15 +128,15 @@ cli_expand_var_generate(clicon_handle h,
cprintf(cb0, " fraction-digits:%u", fraction_digits); cprintf(cb0, " fraction-digits:%u", fraction_digits);
cprintf(cb0, " %s(\"candidate\",\"%s\")>", cprintf(cb0, " %s(\"candidate\",\"%s\")>",
GENERATE_EXPAND_XMLDB, GENERATE_EXPAND_XMLDB,
xkfmt); api_path_fmt);
retval = 0; retval = 0;
done: done:
if (xkfmt) if (api_path_fmt)
free(xkfmt); free(api_path_fmt);
return retval; 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] h clicon handle
* @param[in] ys yang_stmt of the node at hand * @param[in] ys yang_stmt of the node at hand
* @param[in] cb0 The string where the result format string is inserted. * @param[in] cb0 The string where the result format string is inserted.
@ -149,15 +148,16 @@ cli_callback_generate(clicon_handle h,
cbuf *cb0) cbuf *cb0)
{ {
int retval = -1; 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; goto done;
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt); cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK,
api_path_fmt);
retval = 0; retval = 0;
done: done:
if (xkfmt) if (api_path_fmt)
free(xkfmt); free(api_path_fmt);
return retval; return retval;
} }
@ -166,8 +166,7 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level); 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]*"). * patterns, (eg regexp:"[0.9]*").
*/ */
static int static int
@ -276,6 +275,21 @@ yang2cli_var_sub(clicon_handle h,
if (helptext) if (helptext)
cprintf(cb0, "(\"%s\")", helptext); cprintf(cb0, "(\"%s\")", helptext);
if (completion){ if (completion){
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, if (cli_expand_var_generate(h, ys, cvtype, cb0,
options, fraction_digits) < 0) options, fraction_digits) < 0)
goto done; goto done;
@ -403,7 +417,6 @@ yang2cli_leaf(clicon_handle h,
return retval; return retval;
} }
static int static int
yang2cli_container(clicon_handle h, yang2cli_container(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,

View file

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

View file

@ -82,6 +82,9 @@ int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(clicon_handle h, cvec *vars, cvec *argv); int cli_merge(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(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_del(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv); int cli_delv(clicon_handle h, cvec *vars, cvec *argv);

View file

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

View file

@ -69,11 +69,12 @@ notfound(FCGX_Request *r)
path = FCGX_GetParam("DOCUMENT_URI", r->envp); path = FCGX_GetParam("DOCUMENT_URI", r->envp);
FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */ 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, "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", FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n",
path); path);
return 0; return 0;
} }
int int
badrequest(FCGX_Request *r) badrequest(FCGX_Request *r)
{ {
@ -89,6 +90,16 @@ badrequest(FCGX_Request *r)
return 0; 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 */ /*! Specialization of clicon_debug with xml tree */
int int
clicon_debug_xml(int dbglevel, clicon_debug_xml(int dbglevel,

View file

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

View file

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

View file

@ -34,8 +34,7 @@
*/ */
/* /*
* See draft-ietf-netconf-restconf-13.txt [draft] * See rfc8040
* See draft-ietf-netconf-restconf-17.txt [draft]
* sudo apt-get install libfcgi-dev * sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi * gcc -o fastcgi fastcgi.c -lfcgi
@ -120,7 +119,7 @@ Mapping netconf error-tag -> status code
#include "restconf_methods.h" #include "restconf_methods.h"
/*! REST OPTIONS method /*! REST OPTIONS method
* According to restconf (Sec 3.5.1.1 in [draft]) * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @code * @code
@ -227,7 +226,7 @@ api_data_head(clicon_handle h,
} }
/*! REST GET method /*! REST GET method
* According to restconf (Sec 3.5.1.1 in [draft]) * According to restconf
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element * @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); 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 /*! REST POST method
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.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] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data * @param[in] data Stream input data
* @note We map post to edit-config create.
POST: 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 If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in 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 If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned. and a "409 Conflict" status-line MUST be returned.
* Netconf: <edit-config> (nc:operation="create") | invoke an RPC operation * @example * Netconf: <edit-config> (nc:operation="create") | invoke an RPC operation * @example
*/ */
int int
api_data_post(clicon_handle h, api_data_post(clicon_handle h,
@ -349,13 +293,90 @@ api_data_post(clicon_handle h,
cvec *qvec, cvec *qvec,
char *data) 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 /*! Generic REST PUT method
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.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] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
@ -379,14 +400,96 @@ api_data_put(clicon_handle h,
cvec *qvec, cvec *qvec,
char *data) char *data)
{ {
/* XXX: OP_CREATE? */ int retval = -1;
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_REPLACE); 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 /*! Generic REST PATCH method
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.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] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec * @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] qvec Vector of query string (QUERY_STRING)
@ -402,13 +505,15 @@ api_data_patch(clicon_handle h,
cvec *qvec, cvec *qvec,
char *data) 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 /*! Generic REST DELETE method
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.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 * @param[in] pi Offset, where path starts
* Example: * Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 * 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 retval = -1;
int i; 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); 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++) for (i=0; i<pi; i++)
api_path = index(api_path+1, '/'); 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", if (clicon_rpc_edit_config(h, "candidate",
OP_DELETE, OP_NONE,
api_path, cbuf_get(cbx)) < 0){
"<config/>") < 0){
notfound(r); notfound(r);
goto done; goto done;
} }
@ -440,6 +570,10 @@ api_data_delete(clicon_handle h,
FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "\r\n");
retval = 0; retval = 0;
done: done:
if (cbx)
cbuf_free(cbx);
if (xtop)
xml_free(xtop);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval; return retval;
} }

4
configure vendored
View file

@ -2172,8 +2172,8 @@ _ACEOF
# Bind to specific CLIgen version # Bind to specific CLIgen version
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}_PRE" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}" >&5
$as_echo "CLIXON version is ${CLIXON_VERSION}_PRE" >&6; } $as_echo "CLIXON version is ${CLIXON_VERSION}" >&6; }
ac_aux_dir= ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do

View file

@ -62,7 +62,7 @@ AC_SUBST(CLIXON_VERSION_MAJOR)
AC_SUBST(CLIXON_VERSION_MINOR) AC_SUBST(CLIXON_VERSION_MINOR)
AC_SUBST(CLIGEN_VERSION) # Bind to specific CLIgen version 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_CANONICAL_TARGET
AC_SUBST(CC) AC_SUBST(CC)

View file

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

View file

@ -65,7 +65,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC) $(PLUGIN): $(SRC)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS) $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean: clean:
rm -f $(PLUGIN) $(OBJS) *.core rm -f $(PLUGIN) $(OBJS) *.core

View file

@ -50,15 +50,15 @@
* The relations between the functions and formats are as follows: * 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 |----------------> * | list aa,leaf k | ----------------->| /aa=%s |---------------->
* +-----------------+ +-----------------+ * +-----------------+ +-----------------+
* | * |
* | xmlkeyfmt2key * | api_path_fmt2api_path
* | k=17 * | k=17
* v * v
* +-------------------+ +-----------------+ * +-------------------+ +-----------------+
* | xml-tree/cxobj | xmlkey2xml | xmlkey RFC3986| * | xml-tree/cxobj | xmlkey2xml |api_path RFC3986|
* | <aa><k>17</k></aa>| <------------- | /aa=17 | * | <aa><k>17</k></aa>| <------------- | /aa=17 |
* +-------------------+ +-----------------+ * +-------------------+ +-----------------+
* *
@ -68,7 +68,7 @@
* *
* Paths through the code (for coverage) * Paths through the code (for coverage)
* cli_callback_generate +----------------+ * cli_callback_generate +----------------+
* cli_expand_var_generate | yang2xmlkeyfmt | * cli_expand_var_generate | yang2api_path_fmt |
* yang -------------> | | * yang -------------> | |
* +----------------+ * +----------------+
* xmldb_get_tree * xmldb_get_tree
@ -615,7 +615,6 @@ kv_get(xmldb_handle xh,
struct db_pair *pairs; struct db_pair *pairs;
cxobj *xt = NULL; cxobj *xt = NULL;
clicon_debug(2, "%s", __FUNCTION__); clicon_debug(2, "%s", __FUNCTION__);
if (kv_db2file(kh, db, &dbfile) < 0) if (kv_db2file(kh, db, &dbfile) < 0)
goto done; goto done;
@ -761,9 +760,40 @@ put(char *dbfile,
goto done; goto done;
} }
case OP_REMOVE: case OP_REMOVE:
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) if (db_del(dbfile, xk) < 0)
goto done; goto done;
break; break;
}
break;
case OP_NONE: case OP_NONE:
break; break;
} }
@ -782,497 +812,10 @@ put(char *dbfile,
cbuf_free(cbxk); cbuf_free(cbxk);
if (bodyenc) if (bodyenc)
free(bodyenc); free(bodyenc);
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__); unchunk_group(__FUNCTION__);
return retval; 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 /*! Modify database provided an xml tree and an operation
* *
@ -1299,7 +842,6 @@ int
kv_put(xmldb_handle xh, kv_put(xmldb_handle xh,
char *db, char *db,
enum operation_type op, enum operation_type op,
char *api_path,
cxobj *xt) cxobj *xt)
{ {
int retval = -1; int retval = -1;
@ -1309,9 +851,6 @@ kv_put(xmldb_handle xh,
yang_spec *yspec; yang_spec *yspec;
char *dbfilename = NULL; 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){ if ((yspec = kh->kh_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
@ -1324,14 +863,6 @@ kv_put(xmldb_handle xh,
if (db_init(dbfilename) < 0) if (db_init(dbfilename) < 0)
goto done; 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__); // clicon_log(LOG_WARNING, "%s", __FUNCTION__);
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
@ -1346,7 +877,6 @@ kv_put(xmldb_handle xh,
) < 0) ) < 0)
goto done; goto done;
} }
}
retval = 0; retval = 0;
done: done:
if (dbfilename) if (dbfilename)
@ -1401,7 +931,6 @@ kv_lock(xmldb_handle xh,
{ {
int retval = -1; int retval = -1;
// struct kv_handle *kh = handle(xh); // struct kv_handle *kh = handle(xh);
fprintf(stderr, "%s %s %d\n", __FUNCTION__, db, pid);
if (strcmp("running", db) == 0) if (strcmp("running", db) == 0)
_running_locked = pid; _running_locked = pid;
else if (strcmp("candidate", db) == 0) else if (strcmp("candidate", db) == 0)
@ -1413,8 +942,6 @@ kv_lock(xmldb_handle xh,
goto done; goto done;
} }
clicon_debug(1, "%s: locked by %u", db, pid); 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; retval = 0;
done: done:
return retval; return retval;
@ -1434,7 +961,6 @@ kv_unlock(xmldb_handle xh,
{ {
int retval = -1; int retval = -1;
// struct kv_handle *kh = handle(xh); // struct kv_handle *kh = handle(xh);
fprintf(stderr, "%s %s\n", __FUNCTION__, db);
if (strcmp("running", db) == 0) if (strcmp("running", db) == 0)
_running_locked = 0; _running_locked = 0;
else if (strcmp("candidate", db) == 0) else if (strcmp("candidate", db) == 0)
@ -1485,9 +1011,6 @@ kv_islocked(xmldb_handle xh,
int retval = -1; int retval = -1;
// struct kv_handle *kh = handle(xh); // 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) if (strcmp("running", db) == 0)
retval = _running_locked; retval = _running_locked;
else if (strcmp("candidate", db) == 0) else if (strcmp("candidate", db) == 0)

View file

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

View file

@ -64,7 +64,7 @@ all: $(PLUGIN)
-include $(DESTDIR)$(datarootdir)/clixon/clixon.mk -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk
$(PLUGIN): $(SRC) $(PLUGIN): $(SRC)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -shared -o $@ -lc $^ $(LIBS) $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean: clean:
rm -f $(PLUGIN) $(OBJS) *.core rm -f $(PLUGIN) $(OBJS) *.core

View file

@ -308,6 +308,7 @@ singleconfigroot(cxobj *xt,
if (xml_free(xt) < 0) if (xml_free(xt) < 0)
goto done; goto done;
*xp = x; *xp = x;
break;
} }
retval = 0; retval = 0;
done: done:
@ -446,281 +447,19 @@ text_get(xmldb_handle xh,
return retval; 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 /*! Given a modification tree, check existing matching child in the base tree
* param[in] x0 Base tree node * param[in] x0 Base tree node
* param[in] x1c Modification tree child * param[in] x1c Modification tree child
* param[in] yc Yang spec of 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, match_base_child(cxobj *x0,
cxobj *x1c, cxobj *x1c,
yang_stmt *yc) yang_stmt *yc,
cxobj **x0cp)
{ {
int retval = -1;
cxobj *x0c = NULL; cxobj *x0c = NULL;
char *keyname; char *keyname;
cvec *cvk = NULL; cvec *cvk = NULL;
@ -777,10 +516,12 @@ match_base_child(cxobj *x0,
x0c = xml_find(x0, cname); x0c = xml_find(x0, cname);
break; break;
} }
*x0cp = x0c;
retval = 0;
done: done:
if (cvk) if (cvk)
cvec_free(cvk); cvec_free(cvk);
return x0c; return retval;
} }
/*! Modify a base tree x0 with x1 with yang spec y according to operation op /*! Modify a base tree x0 with x1 with yang spec y according to operation op
@ -788,50 +529,36 @@ match_base_child(cxobj *x0,
* @param[in] x0p Parent of x0 * @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base * @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @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) * @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 * Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c * @see put in clixon_keyvalue.c
*/ */
static int static int
text_modify(cxobj *x0, text_modify(cxobj *x0,
yang_node *y0,
cxobj *x0p, cxobj *x0p,
cxobj *x1, cxobj *x1,
enum operation_type op, enum operation_type op)
yang_node *y,
yang_spec *yspec)
{ {
int retval = -1; int retval = -1;
char *opstr; char *opstr;
char *name; char *x1name;
char *cname; /* child name */ char *x1cname; /* child name */
cxobj *x0c; /* base child */ cxobj *x0c; /* base child */
cxobj *x0b; /* base body */ cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */ cxobj *x1c; /* mod child */
char *x1bstr; /* mod body string */ char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */ 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 */ /* 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) if (xml_operation(opstr, &op) < 0)
goto done; goto done;
if (x1 == NULL){ x1name = xml_name(x1);
switch(op){ if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
case OP_REPLACE:
if (x0)
xml_purge(x0);
case OP_CREATE:
case OP_MERGE:
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); x1bstr = xml_body(x1);
switch(op){ switch(op){
case OP_CREATE: case OP_CREATE:
@ -839,14 +566,21 @@ text_modify(cxobj *x0,
clicon_err(OE_XML, 0, "Object to create already exists"); clicon_err(OE_XML, 0, "Object to create already exists");
goto done; goto done;
} }
/* Fall thru */ case OP_NONE: /* fall thru */
case OP_NONE: /* XXX */
case OP_MERGE: case OP_MERGE:
case OP_REPLACE: case OP_REPLACE:
if (x0==NULL){ if (x0==NULL){
if ((x0 = xml_new_spec(name, x0p, y)) == NULL) // int iamkey=0;
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done; 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) if (op==OP_NONE)
#endif
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
if (x1bstr){ /* empty type does not have body */ if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0)) == NULL) if ((x0b = xml_new("body", x0)) == NULL)
@ -869,7 +603,7 @@ text_modify(cxobj *x0,
clicon_err(OE_XML, 0, "Object to delete does not exist"); clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done; goto done;
} }
case OP_REMOVE: case OP_REMOVE: /* fall thru */
if (x0) if (x0)
xml_purge(x0); xml_purge(x0);
break; break;
@ -877,53 +611,40 @@ text_modify(cxobj *x0,
break; break;
} /* switch op */ } /* switch op */
} /* if LEAF|LEAF_LIST */ } /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER */ else { /* eg Y_CONTAINER, Y_LIST */
switch(op){ switch(op){
case OP_CREATE: case OP_CREATE:
/* top-level object <config/> is a special case, ie when if (x0){
* 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"); clicon_err(OE_XML, 0, "Object to create already exists");
goto done; goto done;
} }
case OP_REPLACE: case OP_REPLACE: /* fall thru */
/* top-level object <config/> is a special case, ie when if (x0){
* x0 parent is NULL,
* or x1 is empty
*/
if ((x0p && x0) ||
(x0p==NULL && xml_child_nr(x1) == 0)){
xml_purge(x0); xml_purge(x0);
x0 = NULL; x0 = NULL;
} }
case OP_NONE: /* XXX */ case OP_NONE: /* fall thru */
case OP_MERGE: case OP_MERGE:
if (x0==NULL){ if (x0==NULL){
if ((x0 = xml_new_spec(name, x0p, y)) == NULL) if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
goto done; goto done;
if (op==OP_NONE) if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
} }
/* Loop through children of the modification tree */ /* Loop through children of the modification tree */
x1c = NULL; x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
cname = xml_name(x1c); x1cname = xml_name(x1c);
/* Get yang spec of the child */ /* Get yang spec of the child */
if (y == NULL) if ((yc = yang_find_syntax(y0, x1cname)) == NULL){
yc = yang_find_topnode(yspec, cname); /* still NULL for config */ clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
else{
if ((yc = yang_find_syntax(y, cname)) == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", cname);
goto done; goto done;
} }
}
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
x0c = yc?match_base_child(x0, x1c, yc):NULL; x0c = NULL;
if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0) if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
goto done; goto done;
} }
break; break;
@ -932,7 +653,7 @@ text_modify(cxobj *x0,
clicon_err(OE_XML, 0, "Object to delete does not exist"); clicon_err(OE_XML, 0, "Object to delete does not exist");
goto done; goto done;
} }
case OP_REMOVE: case OP_REMOVE: /* fall thru */
if (x0) if (x0)
xml_purge(x0); xml_purge(x0);
break; break;
@ -940,13 +661,79 @@ text_modify(cxobj *x0,
break; break;
} /* CONTAINER switch op */ } /* CONTAINER switch op */
} /* else Y_CONTAINER */ } /* else Y_CONTAINER */
} /* x1 != NULL */
// ok: // ok:
retval = 0; retval = 0;
done: done:
return retval; 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 /*! Modify database provided an xml tree and an operation
* *
* @param[in] xh XMLDB handle * @param[in] xh XMLDB handle
@ -954,9 +741,7 @@ text_modify(cxobj *x0,
* @param[in] op OP_MERGE: just add it. * @param[in] op OP_MERGE: just add it.
* OP_REPLACE: first delete whole database * OP_REPLACE: first delete whole database
* OP_NONE: operation attribute in xml determines operation * 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] x1 xml-tree to merge/replace. Top-level symbol is 'config'.
])
* @param[in] xadd xml-tree to merge/replace. Top-level symbol is 'config'.
* Should be empty or '<config/>' if delete? * Should be empty or '<config/>' if delete?
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
@ -968,40 +753,32 @@ text_modify(cxobj *x0,
* if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0) * if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0)
* err; * err;
* @endcode * @endcode
*/ y */
int int
text_put(xmldb_handle xh, text_put(xmldb_handle xh,
char *db, char *db,
enum operation_type op, enum operation_type op,
char *api_path, cxobj *x1)
cxobj *xmod)
{ {
int retval = -1; int retval = -1;
struct text_handle *th = handle(xh); struct text_handle *th = handle(xh);
char *dbfile = NULL; char *dbfile = NULL;
int fd = -1; int fd = -1;
cbuf *cb = NULL; cbuf *cb = NULL;
cbuf *xpcb = NULL; /* xpath cbuf */
yang_spec *yspec; yang_spec *yspec;
cxobj *xt = NULL; cxobj *x0 = NULL;
cxobj *xbase = NULL;
cxobj *xbasep = NULL; /* parent */
cxobj *xc;
cxobj *xnew = NULL;
yang_node *y = 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) if (text_db2file(th, db, &dbfile) < 0)
goto done; goto done;
if (dbfile==NULL){ if (dbfile==NULL){
clicon_err(OE_XML, 0, "dbfile NULL"); clicon_err(OE_XML, 0, "dbfile NULL");
goto done; 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){ if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
@ -1011,59 +788,47 @@ text_put(xmldb_handle xh,
goto done; goto done;
} }
/* Parse file into XML tree */ /* 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; goto done;
/* Always assert a top-level called "config". /* Always assert a top-level called "config".
To ensure that, deal with two cases: To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */ 1. File is empty <top/> -> rename top-level to "config" */
if (xml_child_nr(xt) == 0){ if (xml_child_nr(x0) == 0){
if (xml_name_set(xt, "config") < 0) if (xml_name_set(x0, "config") < 0)
goto done; goto done;
} }
/* 2. File is not empty <top><config>...</config></top> -> replace root */ /* 2. File is not empty <top><config>...</config></top> -> replace root */
else{ 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; goto done;
} }
/* Here xt looks like: <config>...</config> */ /* Here x0 looks like: <config>...</config> */
/* Validate existing config tree */ if (strcmp(xml_name(x0),"config")!=0){
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) clicon_err(OE_XML, 0, "Top-level symbol is %s, expected \"config\"",
xml_name(x0));
goto done; 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){ if (text_modify_top(x0, x1, yspec, op) < 0)
/* 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; goto done;
/* Remove NONE nodes if all subs recursively are also NONE */ /* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged(xt, XML_FLAG_NONE, 0, NULL) <0) if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0)
goto done; goto done;
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)XML_FLAG_NONE) < 0) (void*)XML_FLAG_NONE) < 0)
goto done; goto done;
// output: // output:
@ -1072,7 +837,7 @@ text_put(xmldb_handle xh,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
if (clicon_xml2cbuf(cb, xt, 0, 1) < 0) if (clicon_xml2cbuf(cb, x0, 0, 1) < 0)
goto done; goto done;
/* Reopen file in write mode */ /* Reopen file in write mode */
close(fd); close(fd);
@ -1092,12 +857,8 @@ text_put(xmldb_handle xh,
close(fd); close(fd);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (xpcb) if (x0)
cbuf_free(xpcb); xml_free(x0);
if (xt)
xml_free(xt);
if (xnew)
xml_free(xnew);
return retval; return retval;
} }
@ -1275,6 +1036,8 @@ text_delete(xmldb_handle xh,
} }
retval = 0; retval = 0;
done: done:
if (filename)
free(filename);
return retval; return retval;
} }

View file

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

View file

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

View file

@ -47,7 +47,7 @@ int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr); int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret); 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, 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_copy_config(clicon_handle h, char *db1, char *db2);
int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db);

View file

@ -45,7 +45,7 @@ enum operation_type{ /* edit-configo */
OP_REPLACE,/* replace or create config-data */ OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */ OP_CREATE, /* create config data, error if exist */
OP_DELETE, /* delete config data, error if it does not 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 OP_NONE
}; };

View file

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

View file

@ -61,14 +61,15 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
cxobj ***first, size_t *firstlen, cxobj ***first, size_t *firstlen,
cxobj ***second, size_t *secondlen, cxobj ***second, size_t *secondlen,
cxobj ***changed1, cxobj ***changed2, size_t *changedlen); cxobj ***changed1, cxobj ***changed2, size_t *changedlen);
int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt); int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt);
int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk); int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path);
int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk); 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_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark);
int xml_default(cxobj *x, void *arg); int xml_default(cxobj *x, void *arg);
int xml_order(cxobj *x, void *arg); int xml_order(cxobj *x, void *arg);
int xml_sanity(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_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
int api_path2xpath(yang_spec *yspec, char *api_path, 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_ */ #endif /* _CLIXON_XML_MAP_H_ */

View file

@ -224,7 +224,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/ */
/* top: json -> value is also possible */ /* 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");} value : J_TRUE { json_current_body(_JY, "true");}
@ -259,6 +259,7 @@ valuelist : value
/* quoted string */ /* quoted string */
string : J_DQ ustring J_DQ { clicon_debug(2,"string->\" ustring \"");$$=$2; } string : J_DQ ustring J_DQ { clicon_debug(2,"string->\" ustring \"");$$=$2; }
| J_DQ J_DQ { clicon_debug(2,"string->\" ustring \"");$$=strdup(""); }
; ;
/* unquoted string */ /* unquoted string */

View file

@ -119,9 +119,12 @@ format_str2int(char *str)
return fv?fv->fv_int:-1; 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 * @param[in] format Variable agrument list format an XML netconf string
* @retval msg Clicon message to send to eg clicon_msg_send() * @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 * struct clicon_msg *
clicon_msg_encode(char *format, ...) clicon_msg_encode(char *format, ...)

View file

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

View file

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

View file

@ -121,7 +121,9 @@ tleaf(cxobj *x)
* @param[in] level print 4 spaces per level in front of each line * @param[in] level print 4 spaces per level in front of each line
*/ */
int int
xml2txt(FILE *f, cxobj *x, int level) xml2txt(FILE *f,
cxobj *x,
int level)
{ {
cxobj *xe = NULL; cxobj *xe = NULL;
int children=0; int children=0;
@ -799,11 +801,11 @@ xml_diff(yang_spec *yspec,
* yang: container a -> list b -> key c -> leaf d * yang: container a -> list b -> key c -> leaf d
* xpath: /a/b/%s/d * xpath: /a/b/%s/d
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] inclkey If inclkey then include key leaf (eg last leaf d in ex) * @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] cbuf keyfmt * @param[out] cb api_path_fmt,
*/ */
static int static int
yang2xmlkeyfmt_1(yang_stmt *ys, yang2api_path_fmt_1(yang_stmt *ys,
int inclkey, int inclkey,
cbuf *cb) cbuf *cb)
{ {
@ -817,7 +819,7 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
if (yp != NULL && if (yp != NULL &&
yp->yn_keyword != Y_MODULE && yp->yn_keyword != Y_MODULE &&
yp->yn_keyword != Y_SUBMODULE){ 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; goto done;
} }
if (inclkey){ if (inclkey){
@ -825,11 +827,14 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
cprintf(cb, "/%s", ys->ys_argument); cprintf(cb, "/%s", ys->ys_argument);
} }
else{ 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) if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ 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) if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument); cprintf(cb, "/%s", ys->ys_argument);
} }
@ -866,19 +871,19 @@ yang2xmlkeyfmt_1(yang_stmt *ys,
return retval; 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. * Recursively construct it to the top.
* Example: * Example:
* yang: container a -> list b -> key c -> leaf d * yang: container a -> list b -> key c -> leaf d
* xpath: /a/b=%s/d * api_path: /a/b=%s/d
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] inclkey If !inclkey then dont include key leaf * @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] xkfmt XML key format. Needs to be freed after use. * @param[out] api_path_fmt XML api path. Needs to be freed after use.
*/ */
int int
yang2xmlkeyfmt(yang_stmt *ys, yang2api_path_fmt(yang_stmt *ys,
int inclkey, int inclkey,
char **xkfmt) char **api_path_fmt)
{ {
int retval = -1; int retval = -1;
cbuf *cb = NULL; cbuf *cb = NULL;
@ -887,9 +892,9 @@ yang2xmlkeyfmt(yang_stmt *ys,
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
if (yang2xmlkeyfmt_1(ys, inclkey, cb) < 0) if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
goto done; goto done;
if ((*xkfmt = strdup(cbuf_get(cb))) == NULL){ if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }
@ -904,19 +909,20 @@ yang2xmlkeyfmt(yang_stmt *ys,
/*! Transform an xml key format and a vector of values to an XML key /*! 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() * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example: * Example:
* xmlkeyfmt: /aaa/%s * xmlkeyfmt: /aaa/%s/name
* cvv: key=17 * cvv: key=17
* xmlkey: /aaa/17 * xmlkey: /aaa/17/name
* @param[in] xkfmt XML key format, eg /aaa/%s * @param[in] api_path_fmt XML key format, eg /aaa/%s/name
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] xk XML key, eg /aaa/17. Free after use * @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,.. * @note first and last elements of cvv are not used,..
* @see cli_dbxml where this function is called * @see cli_dbxml where this function is called
*/ */
int int
xmlkeyfmt2key(char *xkfmt, api_path_fmt2api_path(char *api_path_fmt,
cvec *cvv, cvec *cvv,
char **xk) char **api_path)
{ {
int retval = -1; int retval = -1;
char c; char c;
@ -927,16 +933,15 @@ xmlkeyfmt2key(char *xkfmt,
char *str; char *str;
char *strenc=NULL; char *strenc=NULL;
/* Sanity check */ /* Sanity check */
#if 1 #if 0
j = 0; /* Count % */ j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++) for (i=0; i<strlen(api_path_fmt); i++)
if (xkfmt[i] == '%') if (api_path_fmt[i] == '%')
j++; j++;
if (j+2 < cvec_len(cvv)) { if (j+2 < cvec_len(cvv)) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s", clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt, api_path_fmt,
j, j,
cvec_len(cvv), cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0))); cv_string_get(cvec_i(cvv, 0)));
@ -948,8 +953,8 @@ xmlkeyfmt2key(char *xkfmt,
goto done; goto done;
} }
j = 1; /* j==0 is cli string */ j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){ for (i=0; i<strlen(api_path_fmt); i++){
c = xkfmt[i]; c = api_path_fmt[i];
if (esc){ if (esc){
esc = 0; esc = 0;
if (c!='s') if (c!='s')
@ -967,10 +972,13 @@ xmlkeyfmt2key(char *xkfmt,
else else
if (c == '%') if (c == '%')
esc++; esc++;
else if (c == '/'){
cprintf(cb, "%c", c);
}
else else
cprintf(cb, "%c", c); 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"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }
@ -993,14 +1001,14 @@ xmlkeyfmt2key(char *xkfmt,
* xmlkeyfmt: /ip/me/%s (if key) * xmlkeyfmt: /ip/me/%s (if key)
* cvv: - * cvv: -
* xmlkey: /ipv4/me/a * xmlkey: /ipv4/me/a
* @param[in] xkfmt XML key format * @param[in] api_path_fmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in xkfmt * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] xk XPATH * @param[out] xpath XPATH
*/ */
int int
xmlkeyfmt2xpath(char *xkfmt, api_path_fmt2xpath(char *api_path_fmt,
cvec *cvv, cvec *cvv,
char **xk) char **xpath)
{ {
int retval = -1; int retval = -1;
char c; char c;
@ -1013,14 +1021,14 @@ xmlkeyfmt2xpath(char *xkfmt,
int skip = 0; int skip = 0;
/* Sanity check: count '%' */ /* Sanity check: count '%' */
#if 1 #if 0
j = 0; /* Count % */ j = 0; /* Count % */
for (i=0; i<strlen(xkfmt); i++) for (i=0; i<strlen(api_path_fmt); i++)
if (xkfmt[i] == '%') if (api_path_fmt[i] == '%')
j++; j++;
if (j < cvec_len(cvv)-1) { if (j < cvec_len(cvv)-1) {
clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s", clicon_log(LOG_WARNING, "%s xmlkey format string mismatch(j=%d, cvec_len=%d): %s",
xkfmt, api_path_fmt,
j, j,
cvec_len(cvv), cvec_len(cvv),
cv_string_get(cvec_i(cvv, 0))); cv_string_get(cvec_i(cvv, 0)));
@ -1032,8 +1040,8 @@ xmlkeyfmt2xpath(char *xkfmt,
goto done; goto done;
} }
j = 1; /* j==0 is cli string */ j = 1; /* j==0 is cli string */
for (i=0; i<strlen(xkfmt); i++){ for (i=0; i<strlen(api_path_fmt); i++){
c = xkfmt[i]; c = api_path_fmt[i];
if (esc){ if (esc){
esc = 0; esc = 0;
if (c!='s') if (c!='s')
@ -1059,13 +1067,13 @@ xmlkeyfmt2xpath(char *xkfmt,
if (skip) if (skip)
skip=0; skip=0;
else else
if ((c == '=' || c == ',') && xkfmt[i+1]=='%') if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
; /* skip */ ; /* skip */
else else
cprintf(cb, "%c", c); cprintf(cb, "%c", c);
} }
} }
if ((*xk = strdup4(cbuf_get(cb))) == NULL){ if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
goto done; goto done;
} }
@ -1099,8 +1107,12 @@ xml_tree_prune_flagged(cxobj *xt,
int mark; int mark;
cxobj *x; cxobj *x;
cxobj *xprev; cxobj *xprev;
int iskey;
int anykey=0;
yang_node *yt;
mark = 0; mark = 0;
yt = xml_spec(xt);
x = NULL; x = NULL;
xprev = x = NULL; xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
@ -1109,8 +1121,20 @@ xml_tree_prune_flagged(cxobj *xt,
xprev = x; xprev = x;
continue; /* mark and stop here */ 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) if (xml_tree_prune_flagged(x, flag, test, &submark) < 0)
goto done; goto done;
/* if xt is list and submark anywhere, then key subs are also marked
*/
if (submark) if (submark)
mark++; mark++;
else{ /* Safe with xml_child_each if last */ else{ /* Safe with xml_child_each if last */
@ -1120,6 +1144,22 @@ xml_tree_prune_flagged(cxobj *xt,
} }
xprev = x; 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; retval = 0;
done: done:
if (upmark) if (upmark)
@ -1397,3 +1437,183 @@ api_path2xpath(yang_spec *yspec,
cvec_free(api_path_cvv); cvec_free(api_path_cvv);
return retval; 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;
}

View file

@ -79,23 +79,6 @@ clixon_xml_parseerror(void *_ya, char *s)
return; 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 /* note that we dont handle escaped characters correctly
there may also be some leakage here on NULL return there may also be some leakage here on NULL return
*/ */
@ -185,21 +168,25 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
cxobj *xc; cxobj *xc;
if (strcmp(xml_name(x), name)){ 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); xml_name(x), name);
goto done; goto done;
} }
if (xml_namespace(x)!=NULL){ 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); xml_namespace(x), xml_name(x), name);
goto done; goto done;
} }
/* Strip pretty-print. Ad-hoc algorithm
/* remove all non-terminal bodies (strip pretty-print) */ * 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 (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY)) xc = NULL;
; while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
else{ break;
if (xc != NULL){ /* at least one element */
xc = NULL; xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) { while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_purge(xc); xml_purge(xc);
@ -240,16 +227,22 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya,
name); name);
goto done; 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 (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
;
else{
xc = NULL; xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != 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_value_set(xc, ""); /* XXX remove */ xml_value_set(xc, ""); /* XXX remove */
} }
} }
}
retval = 0; retval = 0;
done: done:
free(name); free(name);
@ -276,8 +269,12 @@ static int
xml_parse_attr(struct xml_parse_yacc_arg *ya, char *id, char *val) xml_parse_attr(struct xml_parse_yacc_arg *ya, char *id, char *val)
{ {
int retval = -1; 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; goto done;
retval = 0; retval = 0;
done: done:

126
lib/src/json_xpath.c Normal file
View 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;
}

View file

@ -5,7 +5,9 @@ testnname=
clixon_cf=/usr/local/etc/routing.conf clixon_cf=/usr/local/etc/routing.conf
# error and exit, arg is optional extra errmsg # error and exit, arg is optional extra errmsg
err(){ err(){
echo "Error in Test$testnr [$testname] $1" echo "Error in Test$testnr [$testname]:"
echo "Expected: $1"
echo "Received: $2"
exit $testnr exit $testnr
} }
@ -21,21 +23,32 @@ new(){
expectfn(){ expectfn(){
cmd=$1 cmd=$1
expect=$2 expect=$2
if [ $# = 3 ]; then
expect2=$3
else
expect2=
fi
ret=`$cmd` ret=`$cmd`
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err "wrong args"
fi fi
# Match if both are empty string # Match if both are empty string
if [ -z "$ret" -a -z "$expect" ]; then if [ -z "$ret" -a -z "$expect" ]; then
return return
fi fi
# grep extended grep # grep extended grep
match=`echo "$ret" | grep -Eo "$expect"` match=`echo "$ret" | grep -EZo "$expect"`
# echo "ret:<$ret>" # echo "ret:\"$ret\""
# echo "expect:$expect" # echo "expect:\"$expect\""
# echo "match:$match" # echo "match:\"$match\""
if [ -z "$match" ]; then 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 fi
} }
@ -56,8 +69,11 @@ EOF
return return
fi fi
match=`echo "$ret" | grep -Eo "$expect"` match=`echo "$ret" | grep -Eo "$expect"`
# echo "ret:\"$ret\""
# echo "expect:\"$expect\""
# echo "match:\"$match\""
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\"" err "$expect" "$ret"
fi fi
} }
@ -74,7 +90,7 @@ expectwait(){
read ret read ret
match=$(echo "$ret" | grep -Eo "$expect"); match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\"" err $expect "$ret"
fi fi
break break
done done

View file

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

View file

@ -26,38 +26,38 @@ new "netconf tests"
new "netconf get empty config" 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>]]>]]>$' 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" 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>eth0</name></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" 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" 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>]]>]]>$' 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" 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>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" 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" 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=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>]]>]]>$" 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" 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>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-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" 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>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Check deleted eth0" #new "Check deleted eth/0/0"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><config><interfaces/></config></data></rpc-reply>]]>]]>$' #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" 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>eth0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-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" 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" 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" 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" new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>" expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"

View file

@ -27,46 +27,63 @@ sleep 1
new "restconf tests" new "restconf tests"
new "restconf options" 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" 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" 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" new "Add subtree to datastore 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' "" expectfn 'curl -sS -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' ""
new "Check eth0 added" new "Check interfaces eth/0/0 added"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth0","type": "eth","enabled": "true"},{ "name": "eth1","type": "eth","enabled": "true"}\]}} 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" new "delete interfaces"
expectfn 'curl -sX POST -d {"interfaces":{"interface":{"name":"eth0","type":"eth","enabled":"true"}}} http://localhost/restconf/data' "Not Found" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces' ""
new "delete eth0" new "Check empty config"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "" expectfn "curl -sSG http://localhost/restconf/data" "^null $"
new "Check deleted eth0" new "Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -sG http://localhost/restconf/data' '{"interfaces": {"interface": {"name": "eth1","type": "eth","enabled": "true"}}} 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" new "Re-post eth/0/0 which should generate error"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' "Not Found" 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" new "Check description added"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' "" 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" new "delete eth/0/0"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' "" expectfn 'curl -sS -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
fi
#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" new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf sudo pkill -u www-data clixon_restconf
new "Kill backend" new "Kill backend"
# Check if still alive # Check if still alive

View file

@ -60,37 +60,37 @@ run(){
# Whole tree operations # Whole tree operations
new "datastore $name put all replace" new "datastore $name put all replace"
expectfn "$datastore $conf put replace / $db" "" expectfn "$datastore $conf put replace $db" ""
new "datastore $name get" new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$" expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all remove" new "datastore $name put all remove"
expectfn "$datastore $conf put remove /" expectfn "$datastore $conf put remove <config/>"
new "datastore $name get" new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$" expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all merge" new "datastore $name put all merge"
expectfn "$datastore $conf put merge / $db" "" expectfn "$datastore $conf put merge $db" ""
new "datastore $name get" new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$" expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put all delete" new "datastore $name put all delete"
expectfn "$datastore $conf put remove /" expectfn "$datastore $conf put remove <config/>"
new "datastore $name get" new "datastore $name get"
expectfn "$datastore $conf get /" "^<config/>$" expectfn "$datastore $conf get /" "^<config/>$"
new "datastore $name put all create" new "datastore $name put all create"
expectfn "$datastore $conf put create / $db" "" expectfn "$datastore $conf put create $db" ""
new "datastore $name get" new "datastore $name get"
expectfn "$datastore $conf get /" "^$db$" expectfn "$datastore $conf get /" "^$db$"
new "datastore $name put top create" 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 # Single key operations
# leaf # leaf
@ -101,43 +101,43 @@ run(){
expectfn "$datastore $conf init" "" expectfn "$datastore $conf init" ""
new "datastore $name create leaf" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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 #leaf-list