merged develop to 3.3.1
This commit is contained in:
commit
6ebfc182f3
40 changed files with 1205 additions and 1334 deletions
|
|
@ -1,4 +1,10 @@
|
||||||
# Clixon CHANGELOG
|
# 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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>"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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. */
|
||||||
|
|
|
||||||
|
|
@ -207,38 +207,64 @@ 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;
|
||||||
len = cvec_len(cvv);
|
}
|
||||||
if (len > 1){
|
arg = cvec_i(argv, 0);
|
||||||
cval = cvec_i(cvv, len-1);
|
api_path_fmt = cv_string_get(arg);
|
||||||
if ((str = cv2str_dup(cval)) == NULL){
|
if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0)
|
||||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
goto done;
|
||||||
goto done;
|
/* Create config top-of-tree */
|
||||||
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xbot = xtop;
|
||||||
|
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((xa = xml_new("operation", xbot)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xml_type_set(xa, CX_ATTR);
|
||||||
|
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (y->yn_keyword != Y_LIST){
|
||||||
|
len = cvec_len(cvv);
|
||||||
|
if (len > 1){
|
||||||
|
cval = cvec_i(cvv, len-1);
|
||||||
|
if ((str = cv2str_dup(cval)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((xb = xml_new("body", xbot)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xml_type_set(xb, CX_BODY);
|
||||||
|
if (xml_value_set(xb, str) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((cb = cbuf_new()) == NULL){
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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,9 +275,24 @@ yang2cli_var_sub(clicon_handle h,
|
||||||
if (helptext)
|
if (helptext)
|
||||||
cprintf(cb0, "(\"%s\")", helptext);
|
cprintf(cb0, "(\"%s\")", helptext);
|
||||||
if (completion){
|
if (completion){
|
||||||
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
if (type && (strcmp(type, "leafref") == 0)){
|
||||||
options, fraction_digits) < 0)
|
yang_stmt *ypath;
|
||||||
goto done;
|
|
||||||
|
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||||
|
clicon_err(OE_XML, 0, "leafref should have path sub");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s ypath:%s\n", __FUNCTION__, ypath->ys_argument);
|
||||||
|
cprintf(cb0, "|<%s:%s", ys->ys_argument,
|
||||||
|
cv_type2str(cvtype));
|
||||||
|
cprintf(cb0, " %s(\"candidate\",\"%s\")>",
|
||||||
|
GENERATE_EXPAND_XMLDB,
|
||||||
|
ypath->ys_argument);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
||||||
|
options, fraction_digits) < 0)
|
||||||
|
goto done;
|
||||||
if (helptext)
|
if (helptext)
|
||||||
cprintf(cb0, "(\"%s\")", helptext);
|
cprintf(cb0, "(\"%s\")", helptext);
|
||||||
cprintf(cb0, ")");
|
cprintf(cb0, ")");
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
4
configure
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,8 +760,39 @@ put(char *dbfile,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
case OP_REMOVE:
|
case OP_REMOVE:
|
||||||
if (db_del(dbfile, xk) < 0)
|
switch (ys->ys_keyword){
|
||||||
|
case Y_LIST:
|
||||||
|
case Y_CONTAINER:{
|
||||||
|
struct db_pair *pairs;
|
||||||
|
int npairs;
|
||||||
|
cbuf *cbrx;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if ((cbrx = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
cprintf(cbrx, "^%s.*$", xk);
|
||||||
|
if ((npairs = db_regexp(dbfile, cbuf_get(cbrx), __FUNCTION__,
|
||||||
|
&pairs, 0)) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Translate to complete xml tree */
|
||||||
|
for (i = 0; i < npairs; i++)
|
||||||
|
if (db_del(dbfile, pairs[i].dp_key) < 0)
|
||||||
|
goto done;
|
||||||
|
if (cbrx)
|
||||||
|
cbuf_free(cbrx);
|
||||||
|
/* Skip recursion, we have deleted whole subtree */
|
||||||
|
retval = 0;
|
||||||
goto done;
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (db_del(dbfile, xk) < 0)
|
||||||
|
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);
|
||||||
|
unchunk_group(__FUNCTION__);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Modify database provided an XML database key and an operation
|
|
||||||
* @param[in] kh Keyvalue handle
|
|
||||||
* @param[in] db Database name
|
|
||||||
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
|
|
||||||
* @param[in] xk XML Key, eg /aa/bb=17/name
|
|
||||||
* @param[in] val Key value, eg "17"
|
|
||||||
|
|
||||||
* @retval 0 OK
|
|
||||||
* @retval -1 Error
|
|
||||||
* @code
|
|
||||||
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
|
|
||||||
* err;
|
|
||||||
* @endcode
|
|
||||||
* @see xmldb_put with xml-tree, no path
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
xmldb_put_xkey(struct kv_handle *kh,
|
|
||||||
char *db,
|
|
||||||
enum operation_type op,
|
|
||||||
char *xk,
|
|
||||||
char *val)
|
|
||||||
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
yang_stmt *y = NULL;
|
|
||||||
yang_stmt *ykey;
|
|
||||||
char **vec = NULL;
|
|
||||||
int nvec;
|
|
||||||
char **valvec = NULL;
|
|
||||||
int nvalvec;
|
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
char *name;
|
|
||||||
char *restval;
|
|
||||||
cg_var *cvi;
|
|
||||||
cvec *cvk = NULL; /* vector of index keys */
|
|
||||||
char *val2 = NULL;
|
|
||||||
cbuf *ckey=NULL; /* partial keys */
|
|
||||||
cbuf *csubkey=NULL; /* partial keys */
|
|
||||||
cbuf *crx=NULL; /* partial keys */
|
|
||||||
char *keyname;
|
|
||||||
int exists;
|
|
||||||
int npairs;
|
|
||||||
struct db_pair *pairs;
|
|
||||||
yang_spec *yspec;
|
|
||||||
char *filename = NULL;
|
|
||||||
|
|
||||||
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
|
|
||||||
if ((yspec = kh->kh_yangspec) == NULL){
|
|
||||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (kv_db2file(kh, db, &filename) < 0)
|
|
||||||
goto done;
|
|
||||||
if (xk == NULL || *xk!='/'){
|
|
||||||
clicon_err(OE_DB, 0, "Invalid api_path: %s", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((ckey = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((csubkey = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
|
|
||||||
goto done;
|
|
||||||
/* Remove trailing '/'. Like in /a/ -> /a */
|
|
||||||
if (nvec > 1 && !strlen(vec[nvec-1]))
|
|
||||||
nvec--;
|
|
||||||
if (nvec < 2){
|
|
||||||
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
i = 1;
|
|
||||||
while (i<nvec){
|
|
||||||
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
|
|
||||||
if ((restval = index(name, '=')) != NULL){
|
|
||||||
*restval = '\0';
|
|
||||||
restval++;
|
|
||||||
}
|
|
||||||
if (i==1){
|
|
||||||
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
|
|
||||||
/* Special handling of "/" */
|
|
||||||
cprintf(ckey, "/");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if ((y = yang_find_topnode(yspec, name)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((op==OP_DELETE || op == OP_REMOVE) &&
|
|
||||||
y->ys_keyword == Y_LEAF &&
|
|
||||||
y->ys_parent->yn_keyword == Y_LIST &&
|
|
||||||
yang_key_match(y->ys_parent, y->ys_argument))
|
|
||||||
/* Special rule if key, dont write last key-name, rm whole*/;
|
|
||||||
else
|
|
||||||
cprintf(ckey, "/%s", name);
|
|
||||||
i++;
|
|
||||||
switch (y->ys_keyword){
|
|
||||||
case Y_LEAF_LIST:
|
|
||||||
if (restval==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cprintf(ckey, "=%s", restval);
|
|
||||||
break;
|
|
||||||
case Y_LIST:
|
|
||||||
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
|
|
||||||
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
|
|
||||||
__FUNCTION__, y->ys_argument);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* The value is a list of keys: <key>[ <key>]* */
|
|
||||||
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (restval==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (valvec)
|
|
||||||
free(valvec);
|
|
||||||
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (cvec_len(cvk) != nvalvec){
|
|
||||||
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cvi = NULL;
|
|
||||||
/* Iterate over individual yang keys */
|
|
||||||
j = 0;
|
|
||||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
||||||
keyname = cv_string_get(cvi);
|
|
||||||
if (j)
|
|
||||||
cprintf(ckey, ",");
|
|
||||||
else
|
|
||||||
cprintf(ckey, "=");
|
|
||||||
val2 = valvec[j++];
|
|
||||||
cprintf(ckey, "%s", val2);
|
|
||||||
cbuf_reset(csubkey);
|
|
||||||
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
|
|
||||||
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
|
|
||||||
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (cvk){
|
|
||||||
cvec_free(cvk);
|
|
||||||
cvk = NULL;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
|
|
||||||
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xk = cbuf_get(ckey);
|
|
||||||
/* final key */
|
|
||||||
switch (op){
|
|
||||||
case OP_CREATE:
|
|
||||||
if ((exists = db_exists(filename, xk)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (exists == 1){
|
|
||||||
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
case OP_MERGE:
|
|
||||||
case OP_REPLACE:
|
|
||||||
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
|
|
||||||
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (db_set(filename, xk, NULL, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
case OP_DELETE:
|
|
||||||
if ((exists = db_exists(filename, xk)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (exists == 0){
|
|
||||||
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exist in database", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
case OP_REMOVE:
|
|
||||||
/* Read in complete database (this can be optimized) */
|
|
||||||
if ((crx = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cprintf(crx, "^%s.*$", xk);
|
|
||||||
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
|
|
||||||
goto done;
|
|
||||||
for (i = 0; i < npairs; i++) {
|
|
||||||
if (db_del(filename, pairs[i].dp_key) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
if (filename)
|
|
||||||
free(filename);
|
|
||||||
if (ckey)
|
|
||||||
cbuf_free(ckey);
|
|
||||||
if (csubkey)
|
|
||||||
cbuf_free(csubkey);
|
|
||||||
if (crx)
|
|
||||||
cbuf_free(crx);
|
|
||||||
if (cvk)
|
|
||||||
cvec_free(cvk);
|
|
||||||
if (vec)
|
|
||||||
free(vec);
|
|
||||||
if (valvec)
|
|
||||||
free(valvec);
|
|
||||||
|
|
||||||
unchunk_group(__FUNCTION__);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Modify database provided an xml tree, a restconf api_path and an operation
|
|
||||||
*
|
|
||||||
* @param[in] kh Keyvalue handle
|
|
||||||
* @param[in] db running or candidate
|
|
||||||
* @param[in] op OP_MERGE: just add it.
|
|
||||||
* OP_REPLACE: first delete whole database
|
|
||||||
* OP_NONE: operation attribute in xml determines operation
|
|
||||||
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
|
|
||||||
* @param[in] xt xml-tree. Top-level symbol is dummy
|
|
||||||
* @retval 0 OK
|
|
||||||
* @retval -1 Error
|
|
||||||
* example:
|
|
||||||
* container top {
|
|
||||||
* list list1 {
|
|
||||||
* key "key1 key2 key3";
|
|
||||||
* is referenced as
|
|
||||||
* /restconf/data/top/list1=a,,foo
|
|
||||||
* @see xmldb_put
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
xmldb_put_restconf_api_path(struct kv_handle *kh,
|
|
||||||
char *db,
|
|
||||||
enum operation_type op,
|
|
||||||
char *xk,
|
|
||||||
cxobj *xt)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
yang_stmt *y = NULL;
|
|
||||||
yang_stmt *ykey;
|
|
||||||
char **vec = NULL;
|
|
||||||
int nvec;
|
|
||||||
#if 0
|
|
||||||
char **valvec = NULL;
|
|
||||||
int nvalvec;
|
|
||||||
int j;
|
|
||||||
char *restval;
|
|
||||||
#endif
|
|
||||||
int i;
|
|
||||||
char *name;
|
|
||||||
cg_var *cvi;
|
|
||||||
cvec *cvk = NULL; /* vector of index keys */
|
|
||||||
char *val2;
|
|
||||||
cbuf *ckey=NULL; /* partial keys */
|
|
||||||
cbuf *csubkey=NULL; /* partial keys */
|
|
||||||
cbuf *crx=NULL; /* partial keys */
|
|
||||||
char *keyname;
|
|
||||||
int exists;
|
|
||||||
int npairs;
|
|
||||||
struct db_pair *pairs;
|
|
||||||
yang_spec *yspec;
|
|
||||||
yang_stmt *ys;
|
|
||||||
char *filename = NULL;
|
|
||||||
char *key;
|
|
||||||
char *keys;
|
|
||||||
|
|
||||||
if ((yspec = kh->kh_yangspec) == NULL){
|
|
||||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (kv_db2file(kh, db, &filename) < 0)
|
|
||||||
goto done;
|
|
||||||
if (xk == NULL || *xk!='/'){
|
|
||||||
clicon_err(OE_DB, 0, "Invalid api path: %s", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((ckey = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((csubkey = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL)
|
|
||||||
goto done;
|
|
||||||
/* Remove trailing '/'. Like in /a/ -> /a */
|
|
||||||
if (nvec > 1 && !strlen(vec[nvec-1]))
|
|
||||||
nvec--;
|
|
||||||
if (nvec < 2){
|
|
||||||
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
i = 1;
|
|
||||||
while (i<nvec){
|
|
||||||
name = vec[i];
|
|
||||||
if ((keys = index(name, '=')) != NULL){
|
|
||||||
*keys = '\0';
|
|
||||||
keys++;
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
if ((restval = index(name, '=')) != NULL){
|
|
||||||
*restval = '\0';
|
|
||||||
restval++;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (i==1){
|
|
||||||
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
|
|
||||||
/* Special handling of "/" */
|
|
||||||
cprintf(ckey, "/");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if ((y = yang_find_topnode(yspec, name)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((op==OP_DELETE || op == OP_REMOVE) &&
|
|
||||||
y->ys_keyword == Y_LEAF &&
|
|
||||||
y->ys_parent->yn_keyword == Y_LIST &&
|
|
||||||
yang_key_match(y->ys_parent, y->ys_argument))
|
|
||||||
/* Special rule if key, dont write last key-name, rm whole*/;
|
|
||||||
else
|
|
||||||
cprintf(ckey, "/%s", name);
|
|
||||||
i++;
|
|
||||||
switch (y->ys_keyword){
|
|
||||||
case Y_LEAF_LIST:
|
|
||||||
/* For leaf-list 'keys' is value, see 3.5.1 in restconf draft */
|
|
||||||
val2 = keys;
|
|
||||||
cprintf(ckey, "/%s", keys);
|
|
||||||
break;
|
|
||||||
case Y_LIST:
|
|
||||||
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
|
|
||||||
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
|
|
||||||
__FUNCTION__, y->ys_argument);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* The value is a list of keys: <key>[ <key>]* */
|
|
||||||
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
|
|
||||||
goto done;
|
|
||||||
#if 0
|
|
||||||
if (restval==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (valvec)
|
|
||||||
free(valvec);
|
|
||||||
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (cvec_len(cvk) != nvalvec){
|
|
||||||
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
j = 0;
|
|
||||||
#endif
|
|
||||||
cvi = NULL;
|
|
||||||
/* Iterate over individual yang keys */
|
|
||||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
|
||||||
keyname = cv_string_get(cvi);
|
|
||||||
#if 0
|
|
||||||
if (j)
|
|
||||||
cprintf(ckey, ",");
|
|
||||||
else
|
|
||||||
cprintf(ckey, "=");
|
|
||||||
val2 = valvec[j++];
|
|
||||||
cprintf(ckey, "%s", val2);
|
|
||||||
#else
|
|
||||||
// val2 = vec[i++]; /* No */
|
|
||||||
val2 = keys;
|
|
||||||
if (i>nvec){ /* XXX >= ? */
|
|
||||||
clicon_err(OE_XML, errno, "List %s without argument", name);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cprintf(ckey, "=%s", val2);
|
|
||||||
#endif
|
|
||||||
cbuf_reset(csubkey);
|
|
||||||
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
|
|
||||||
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
|
|
||||||
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (cvk){
|
|
||||||
cvec_free(cvk);
|
|
||||||
cvk = NULL;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
|
|
||||||
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key = cbuf_get(ckey);
|
|
||||||
/* final key */
|
|
||||||
switch (op){
|
|
||||||
case OP_CREATE:
|
|
||||||
if ((exists = db_exists(filename, key)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (exists == 1){
|
|
||||||
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
case OP_MERGE:
|
|
||||||
case OP_REPLACE:
|
|
||||||
if (xt==NULL){
|
|
||||||
clicon_err(OE_DB, 0, "%s: no xml when yang node %s required",
|
|
||||||
__FUNCTION__, y->ys_argument);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){
|
|
||||||
clicon_err(OE_DB, 0, "%s: child %s not found under node %s",
|
|
||||||
__FUNCTION__, xml_name(xt), y->ys_argument);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
y = ys;
|
|
||||||
if (put(filename, xt, y, op, key) < 0)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
case OP_DELETE:
|
|
||||||
if ((exists = db_exists(filename, key)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (exists == 0){
|
|
||||||
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
case OP_REMOVE:
|
|
||||||
/* Read in complete database (this can be optimized) */
|
|
||||||
if ((crx = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cprintf(crx, "^%s.*$", key);
|
|
||||||
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
|
|
||||||
goto done;
|
|
||||||
for (i = 0; i < npairs; i++) {
|
|
||||||
if (db_del(filename, pairs[i].dp_key) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
// clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
|
||||||
if (filename)
|
|
||||||
free(filename);
|
|
||||||
if (ckey)
|
|
||||||
cbuf_free(ckey);
|
|
||||||
if (csubkey)
|
|
||||||
cbuf_free(csubkey);
|
|
||||||
if (crx)
|
|
||||||
cbuf_free(crx);
|
|
||||||
if (cvk)
|
|
||||||
cvec_free(cvk);
|
|
||||||
if (vec)
|
|
||||||
free(vec);
|
|
||||||
#if 0
|
|
||||||
if (valvec)
|
|
||||||
free(valvec);
|
|
||||||
#endif
|
|
||||||
unchunk_group(__FUNCTION__);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Modify database provided an xml tree and an operation
|
/*! 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,28 +863,19 @@ 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, "%s", __FUNCTION__);
|
||||||
// clicon_log(LOG_WARNING, "xmldb_put_restconf_api_path");
|
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 (xmldb_put_restconf_api_path(kh, db, op, api_path, x) < 0)
|
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
|
||||||
goto done;
|
goto done;
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// clicon_log(LOG_WARNING, "%s", __FUNCTION__);
|
|
||||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
|
|
||||||
if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x));
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (put(dbfilename, /* database name */
|
|
||||||
x, /* xml root node */
|
|
||||||
ys, /* yang statement of xml node */
|
|
||||||
op, /* operation, eg merge/delete */
|
|
||||||
"" /* aggregate xml key */
|
|
||||||
) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
if (put(dbfilename, /* database name */
|
||||||
|
x, /* xml root node */
|
||||||
|
ys, /* yang statement of xml node */
|
||||||
|
op, /* operation, eg merge/delete */
|
||||||
|
"" /* aggregate xml key */
|
||||||
|
) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,165 +529,211 @@ 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);
|
||||||
|
if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){
|
||||||
|
x1bstr = xml_body(x1);
|
||||||
switch(op){
|
switch(op){
|
||||||
case OP_REPLACE:
|
case OP_CREATE:
|
||||||
if (x0)
|
if (x0){
|
||||||
xml_purge(x0);
|
clicon_err(OE_XML, 0, "Object to create already exists");
|
||||||
case OP_CREATE:
|
goto done;
|
||||||
case OP_MERGE:
|
}
|
||||||
break;
|
case OP_NONE: /* fall thru */
|
||||||
|
case OP_MERGE:
|
||||||
|
case OP_REPLACE:
|
||||||
|
if (x0==NULL){
|
||||||
|
// int iamkey=0;
|
||||||
|
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
|
||||||
|
goto done;
|
||||||
|
#if 0
|
||||||
|
/* If it is key I dont want to mark it */
|
||||||
|
if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (!iamkey && op==OP_NONE)
|
||||||
|
#else
|
||||||
|
if (op==OP_NONE)
|
||||||
|
#endif
|
||||||
|
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
||||||
|
if (x1bstr){ /* empty type does not have body */
|
||||||
|
if ((x0b = xml_new("body", x0)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xml_type_set(x0b, CX_BODY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x1bstr){
|
||||||
|
if ((x0b = xml_body_get(x0)) == NULL){
|
||||||
|
if ((x0b = xml_new("body", x0)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xml_type_set(x0b, CX_BODY);
|
||||||
|
}
|
||||||
|
if (xml_value_set(x0b, x1bstr) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OP_DELETE:
|
||||||
|
if (x0==NULL){
|
||||||
|
clicon_err(OE_XML, 0, "Object to delete does not exist");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
case OP_REMOVE: /* fall thru */
|
||||||
|
if (x0)
|
||||||
|
xml_purge(x0);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
} /* switch op */
|
||||||
}
|
} /* if LEAF|LEAF_LIST */
|
||||||
else {
|
else { /* eg Y_CONTAINER, Y_LIST */
|
||||||
assert(xml_type(x1) == CX_ELMNT);
|
switch(op){
|
||||||
name = xml_name(x1);
|
case OP_CREATE:
|
||||||
if (y && (y->yn_keyword == Y_LEAF_LIST || y->yn_keyword == Y_LEAF)){
|
if (x0){
|
||||||
x1bstr = xml_body(x1);
|
clicon_err(OE_XML, 0, "Object to create already exists");
|
||||||
switch(op){
|
goto done;
|
||||||
case OP_CREATE:
|
}
|
||||||
if (x0){
|
case OP_REPLACE: /* fall thru */
|
||||||
clicon_err(OE_XML, 0, "Object to create already exists");
|
if (x0){
|
||||||
|
xml_purge(x0);
|
||||||
|
x0 = NULL;
|
||||||
|
}
|
||||||
|
case OP_NONE: /* fall thru */
|
||||||
|
case OP_MERGE:
|
||||||
|
if (x0==NULL){
|
||||||
|
if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (op==OP_NONE)
|
||||||
|
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
||||||
|
}
|
||||||
|
/* Loop through children of the modification tree */
|
||||||
|
x1c = NULL;
|
||||||
|
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||||
|
x1cname = xml_name(x1c);
|
||||||
|
/* Get yang spec of the child */
|
||||||
|
if ((yc = yang_find_syntax(y0, x1cname)) == NULL){
|
||||||
|
clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* Fall thru */
|
/* See if there is a corresponding node in the base tree */
|
||||||
case OP_NONE: /* XXX */
|
x0c = NULL;
|
||||||
case OP_MERGE:
|
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||||
case OP_REPLACE:
|
|
||||||
if (x0==NULL){
|
|
||||||
if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (op==OP_NONE)
|
|
||||||
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
|
||||||
if (x1bstr){ /* empty type does not have body */
|
|
||||||
if ((x0b = xml_new("body", x0)) == NULL)
|
|
||||||
goto done;
|
|
||||||
xml_type_set(x0b, CX_BODY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (x1bstr){
|
|
||||||
if ((x0b = xml_body_get(x0)) == NULL){
|
|
||||||
if ((x0b = xml_new("body", x0)) == NULL)
|
|
||||||
goto done;
|
|
||||||
xml_type_set(x0b, CX_BODY);
|
|
||||||
}
|
|
||||||
if (xml_value_set(x0b, x1bstr) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OP_DELETE:
|
|
||||||
if (x0==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "Object to delete does not exist");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0)
|
||||||
case OP_REMOVE:
|
|
||||||
if (x0)
|
|
||||||
xml_purge(x0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
} /* switch op */
|
|
||||||
} /* if LEAF|LEAF_LIST */
|
|
||||||
else { /* eg Y_CONTAINER */
|
|
||||||
switch(op){
|
|
||||||
case OP_CREATE:
|
|
||||||
/* top-level object <config/> is a special case, ie when
|
|
||||||
* x0 parent is NULL
|
|
||||||
* or x1 is empty
|
|
||||||
*/
|
|
||||||
if ((x0p && x0) ||
|
|
||||||
(x0p==NULL && xml_child_nr(x1) == 0)){
|
|
||||||
clicon_err(OE_XML, 0, "Object to create already exists");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
case OP_REPLACE:
|
break;
|
||||||
/* top-level object <config/> is a special case, ie when
|
case OP_DELETE:
|
||||||
* x0 parent is NULL,
|
if (x0==NULL){
|
||||||
* or x1 is empty
|
clicon_err(OE_XML, 0, "Object to delete does not exist");
|
||||||
*/
|
goto done;
|
||||||
if ((x0p && x0) ||
|
}
|
||||||
(x0p==NULL && xml_child_nr(x1) == 0)){
|
case OP_REMOVE: /* fall thru */
|
||||||
xml_purge(x0);
|
if (x0)
|
||||||
x0 = NULL;
|
xml_purge(x0);
|
||||||
}
|
break;
|
||||||
case OP_NONE: /* XXX */
|
default:
|
||||||
case OP_MERGE:
|
break;
|
||||||
if (x0==NULL){
|
} /* CONTAINER switch op */
|
||||||
if ((x0 = xml_new_spec(name, x0p, y)) == NULL)
|
} /* else Y_CONTAINER */
|
||||||
goto done;
|
|
||||||
if (op==OP_NONE)
|
|
||||||
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop through children of the modification tree */
|
|
||||||
x1c = NULL;
|
|
||||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
|
||||||
cname = xml_name(x1c);
|
|
||||||
/* Get yang spec of the child */
|
|
||||||
if (y == NULL)
|
|
||||||
yc = yang_find_topnode(yspec, cname); /* still NULL for config */
|
|
||||||
else{
|
|
||||||
if ((yc = yang_find_syntax(y, cname)) == NULL){
|
|
||||||
clicon_err(OE_YANG, errno, "No yang node found: %s", cname);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* See if there is a corresponding node in the base tree */
|
|
||||||
x0c = yc?match_base_child(x0, x1c, yc):NULL;
|
|
||||||
if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OP_DELETE:
|
|
||||||
if (x0==NULL){
|
|
||||||
clicon_err(OE_XML, 0, "Object to delete does not exist");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
case OP_REMOVE:
|
|
||||||
if (x0)
|
|
||||||
xml_purge(x0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
} /* CONTAINER switch op */
|
|
||||||
} /* else Y_CONTAINER */
|
|
||||||
} /* x1 != NULL */
|
|
||||||
// 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;
|
|
||||||
/* Remove NONE nodes if all subs recursively are also NONE */
|
|
||||||
if (xml_tree_prune_flagged(xt, XML_FLAG_NONE, 0, NULL) <0)
|
|
||||||
goto done;
|
goto done;
|
||||||
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
|
||||||
|
/* Remove NONE nodes if all subs recursively are also NONE */
|
||||||
|
if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0)
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||||
(void*)XML_FLAG_NONE) < 0)
|
(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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_ */
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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, ...)
|
||||||
|
|
|
||||||
|
|
@ -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>");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,13 +801,13 @@ 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)
|
||||||
{
|
{
|
||||||
yang_node *yp; /* parent */
|
yang_node *yp; /* parent */
|
||||||
yang_stmt *ykey;
|
yang_stmt *ykey;
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -1094,13 +1102,17 @@ xml_tree_prune_flagged(cxobj *xt,
|
||||||
int test,
|
int test,
|
||||||
int *upmark)
|
int *upmark)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int submark;
|
int submark;
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,26 +168,30 @@ 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);
|
||||||
xc = NULL; /* reset iterator */
|
xc = NULL; /* reset iterator */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -240,14 +227,20 @@ 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))
|
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_value_set(xc, ""); /* XXX remove */
|
xml_value_set(xc, ""); /* XXX remove */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -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
126
lib/src/json_xpath.c
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Utility debug tool
|
||||||
|
* Static Compile: gcc -Wall json_xpath.c -o json_xpath -l clixon
|
||||||
|
gcc -O2 -o json_xpath json_xpath.c clixon_log.o clixon_err.o clixon_json.o clixon_xml.o clixon_xsl.o clixon_json_parse.tab.o clixon_xml_parse.tab.o lex.clixon_json_parse.o lex.clixon_xml_parse.o ../../../cligen/cligen_buf.o ../../../cligen/cligen_var.o ../../../cligen/cligen_gen.o ../../../cligen/cligen_cvec.o ../../../cligen/cligen_handle.o ../../../cligen/getline.o ../../../cligen/cligen_read.o ../../../cligen/cligen_match.o ../../../cligen/cligen_expand.o ../../../cligen/cligen_print.o
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* cligen */
|
||||||
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
|
/* clicon */
|
||||||
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
usage(char *argv0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage:%s <xpath> # XML/JSON expected on stdin\n"
|
||||||
|
"\t-h Help\n"
|
||||||
|
"\t-b Strip to body value (eg \"<a>val</a>\" --> \"val\"\n"
|
||||||
|
"\t-j json input (not xml)\n",
|
||||||
|
argv0);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
cxobj **xv;
|
||||||
|
cxobj *x;
|
||||||
|
cxobj *xn;
|
||||||
|
cxobj *xb;
|
||||||
|
char *xpath;
|
||||||
|
int body = 0;
|
||||||
|
int json = 0;
|
||||||
|
size_t xlen = 0;
|
||||||
|
size_t len;
|
||||||
|
size_t buflen = 128;
|
||||||
|
char *buf;
|
||||||
|
char *p;
|
||||||
|
int retval;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
while ((c = getopt(argc, argv, "?hbj")) != -1)
|
||||||
|
switch (c) {
|
||||||
|
case '?':
|
||||||
|
case 'h':
|
||||||
|
usage(argv[0]);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
body++;
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
json++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
}
|
||||||
|
if (optind >= argc)
|
||||||
|
usage(argv[0]);
|
||||||
|
xpath=argv[optind];
|
||||||
|
|
||||||
|
clicon_log_init("xpath", 0, CLICON_LOG_STDERR);
|
||||||
|
if ((buf = malloc(buflen)) == NULL){
|
||||||
|
perror("malloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* |---------------|-------------|
|
||||||
|
* buf p buf+buflen
|
||||||
|
*/
|
||||||
|
p = buf;
|
||||||
|
memset(p, 0, buflen);
|
||||||
|
while (1){
|
||||||
|
if ((retval = read(0, p, buflen-(p-buf))) < 0){
|
||||||
|
perror("read");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (retval == 0)
|
||||||
|
break;
|
||||||
|
p += retval;
|
||||||
|
len = p-buf;
|
||||||
|
|
||||||
|
if (buf+buflen-p < 10){ /* allocate more */
|
||||||
|
buflen *= 2;
|
||||||
|
if ((buf = realloc(buf, buflen)) == NULL){
|
||||||
|
perror("realloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
p = buf+len;
|
||||||
|
memset(p, 0, (buf+buflen)-p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json){
|
||||||
|
if (json_parse_str(buf, &x) < 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (clicon_xml_parse_str(buf, &x) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (xpath_vec(x, xpath, &xv, &xlen) < 0)
|
||||||
|
return -1;
|
||||||
|
if (xv){
|
||||||
|
for (i=0; i<xlen; i++){
|
||||||
|
xn = xv[i];
|
||||||
|
if (body)
|
||||||
|
xb = xml_find(xn, "body");
|
||||||
|
else
|
||||||
|
xb = xn;
|
||||||
|
if (xb){
|
||||||
|
// xml2json(stdout, xb, 0);
|
||||||
|
clicon_xml2file(stdout, xb, 0, 0);
|
||||||
|
fprintf(stdout,"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(xv);
|
||||||
|
}
|
||||||
|
if (x)
|
||||||
|
xml_free(x);
|
||||||
|
if (buf)
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
test/lib.sh
34
test/lib.sh
|
|
@ -5,7 +5,9 @@ testnname=
|
||||||
clixon_cf=/usr/local/etc/routing.conf
|
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
|
||||||
|
|
|
||||||
|
|
@ -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" ""
|
||||||
|
|
|
||||||
|
|
@ -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>"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue