* New "general-purpose" datastore upgrade callback added which i called once on startup, intended for lo
w-level general upgrades and as a complement to module-specific upgrade. * Called on startup after initial XML parsing, but before module-specific upgrades * Enabled by definign the `.ca_datastore_upgrade` * [General-purpose upgrade documentation](https://clixon-docs.readthedocs.io/en/latest/backend.html#ge neral-purpose) * JSON parse error messages change from ` on line x: syntax error,..` to `json_parse: line x: syntax err or` * Unknown-element error message is more descriptive, eg from `namespace is: urn:example:clixon` to: `Fai led to find YANG spec of XML node: x with parent: xp in namespace urn:example:clixon`. * C-API parse and validation API more capable * `xml_spec_populate` family of functions extended with three-value return values * -1: error, 0: parse OK, 1: parse and YANG binding OK. * `xml_parse` and `json_parse` API changes * Three value returns: -1: error, 0: parse OK, 1: parse and YANG binding OK. * Extended `xml_parse_file2` and `xml_parse_string2` extended API functions with all options available. * New concept called `yang_bind` that defines how XML symbols are bound to YANG after parsing * Existing API same except `xml_parse_file` `endtag` argument moved to `xml_parse_file2` * C-API: Added instrumentation: `xml_size` and `xml_stats_get`. * Fixed: Enabling modstate (CLICON_XMLDB_MODSTATE), changing a revision on a yang, and restarting made the backend daemon exit at start (thanks Matt) * Also: ensure to load `ietf-yang-library.yang ` if CLICON_XMLDB_MODSTATE is set
This commit is contained in:
parent
d665992f7c
commit
9fa5e216c4
80 changed files with 2204 additions and 755 deletions
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -170,20 +171,26 @@ client_get_capabilities(clicon_handle h,
|
|||
{
|
||||
int retval = -1;
|
||||
cxobj *xrstate = NULL; /* xml restconf-state node */
|
||||
cxobj *xcap = NULL; /* xml capabilities node */
|
||||
cbuf *cb = NULL;
|
||||
|
||||
if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
|
||||
goto done;
|
||||
}
|
||||
if ((xcap = xml_new("capabilities", xrstate, yspec)) == NULL)
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
if (xml_parse_va(&xcap, yspec, "<capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability>") < 0)
|
||||
goto done;
|
||||
if (xml_parse_va(&xcap, yspec, "<capability>urn:ietf:params:restconf:capability:depth:1.0</capability>") < 0)
|
||||
}
|
||||
cprintf(cb, "<capabilities>");
|
||||
cprintf(cb, "<capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability>");
|
||||
cprintf(cb, "<capability>urn:ietf:params:restconf:capability:depth:1.0</capability>");
|
||||
cprintf(cb, "</capabilities>");
|
||||
if (xml_parse_string2(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +224,7 @@ client_get_streams(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, 0, "clicon buffer");
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns));
|
||||
|
|
@ -271,11 +278,16 @@ client_statedata(clicon_handle h,
|
|||
yang_stmt *ymod;
|
||||
int ret;
|
||||
char *namespace;
|
||||
cbuf *cb = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
}
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
|
||||
if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
|
||||
|
|
@ -285,7 +297,9 @@ client_statedata(clicon_handle h,
|
|||
clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_va(xret, yspec, "<netconf xmlns=\"%s\"/>", namespace) < 0)
|
||||
|
||||
cprintf(cb, "<netconf xmlns=\"%s\"/>", namespace);
|
||||
if (xml_parse_string2(cbuf_get(cb), YB_TOP, yspec, xret, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
|
||||
goto done;
|
||||
|
|
@ -301,7 +315,9 @@ client_statedata(clicon_handle h,
|
|||
clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_va(xret, yspec, "<restconf-state xmlns=\"%s\"/>", namespace) < 0)
|
||||
cbuf_reset(cb);
|
||||
cprintf(cb, "<restconf-state xmlns=\"%s\"/>", namespace);
|
||||
if (xml_parse_string2(cbuf_get(cb), YB_TOP, yspec, xret, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
|
||||
goto done;
|
||||
|
|
@ -323,6 +339,8 @@ client_statedata(clicon_handle h,
|
|||
retval = 1; /* OK */
|
||||
done:
|
||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
|
|
@ -574,7 +592,7 @@ from_client_edit_config(clicon_handle h,
|
|||
xml_spec_set(xc, NULL);
|
||||
/* Populate XML with Yang spec (why not do this in parser?)
|
||||
*/
|
||||
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xc, yspec, NULL) < 0)
|
||||
goto done;
|
||||
/* Maybe validate xml here as in text_modify_top? */
|
||||
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
|
||||
|
|
@ -980,15 +998,22 @@ from_client_get(clicon_handle h,
|
|||
}
|
||||
/* If not only-state, then read running config
|
||||
* Note xret can be pruned by nacm below and change name and
|
||||
* metrged with state data, so zero-copy cant be used
|
||||
* merged with state data, so zero-copy cant be used
|
||||
* Also, must use external namespace context here due to <filter stmt
|
||||
*/
|
||||
#ifdef VALIDATE_STATE_XML
|
||||
if (xmldb_get0(h, "running", nsc, NULL, 1, &xret, NULL) < 0) {
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
#else
|
||||
if (xmldb_get0(h, "running", nsc, xpath, 1, &xret, NULL) < 0) {
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
#endif
|
||||
/* If not only config,
|
||||
* get state data from plugins as defined by plugin_statedata(), if any
|
||||
*/
|
||||
|
|
@ -1501,7 +1526,7 @@ from_client_msg(clicon_handle h,
|
|||
* should really have been dealt with by decode above
|
||||
* but it still is needed - test_cli debug test fails
|
||||
*/
|
||||
if (xml_spec_populate_rpc_input(h, x, yspec) < 0)
|
||||
if (xml_spec_populate_rpc(x, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_rpc(h, x, &xret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -189,6 +191,7 @@ startup_common(clicon_handle h,
|
|||
clicon_debug(1, "Reading startup config from %s", db);
|
||||
if (xmldb_get0(h, db, NULL, "/", 0, &xt, msd) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "Reading startup config done");
|
||||
/* Clear flags xpath for get */
|
||||
xml_apply0(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||
(void*)(XML_FLAG_MARK|XML_FLAG_CHANGE));
|
||||
|
|
@ -197,20 +200,21 @@ startup_common(clicon_handle h,
|
|||
xt = NULL;
|
||||
goto ok;
|
||||
}
|
||||
if (msd){
|
||||
/* Here xt is old syntax */
|
||||
/* General purpose datastore upgrade */
|
||||
if (clixon_plugin_datastore_upgrade(h, db, xt, msd) < 0)
|
||||
goto done;
|
||||
/* Module-specific upgrade callbacks */
|
||||
if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Here xt is new syntax */
|
||||
}
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "Yang spec not set");
|
||||
goto done;
|
||||
}
|
||||
/* After upgrading, XML tree needs to be sorted and yang spec populated */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xt, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, h) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -210,7 +211,7 @@ nacm_load_external(clicon_handle h)
|
|||
goto done;
|
||||
fd = fileno(f);
|
||||
/* Read configfile */
|
||||
if (xml_parse_file(fd, "</clicon>", yspec, &xt) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xt) < 0)
|
||||
goto done;
|
||||
if (xt == NULL){
|
||||
clicon_err(OE_XML, 0, "No xml tree in %s", filename);
|
||||
|
|
@ -748,6 +749,10 @@ main(int argc,
|
|||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
|
||||
yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
|
||||
goto done;
|
||||
/* Load yang YANG module state */
|
||||
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE") &&
|
||||
yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
|
||||
goto done;
|
||||
/* Here all modules are loaded
|
||||
* Compute and set canonical namespace context
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ clixon_plugin_statedata(clicon_handle h,
|
|||
cbuf_free(ccc);
|
||||
}
|
||||
#endif
|
||||
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(x, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ load_extraxml(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if (xml_parse_file(fd, "</config>", yspec, &xt) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xt) < 0)
|
||||
goto done;
|
||||
/* Replace parent w first child */
|
||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||
|
|
|
|||
|
|
@ -796,7 +796,7 @@ load_config_file(clicon_handle h,
|
|||
clicon_err(OE_UNIX, errno, "open(%s)", filename);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(fd, "</clicon>", NULL, &xt) < 0)
|
||||
if (xml_parse_file(fd, NULL, &xt) < 0)
|
||||
goto done;
|
||||
if (xt == NULL)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -541,7 +541,7 @@ main(int argc, char **argv)
|
|||
cligen_tree_add(cli_cligen(h), treeref, pt);
|
||||
|
||||
if (printgen)
|
||||
cligen_print(stdout, pt, 1); /* pt_print */
|
||||
pt_print(stdout, pt, 1); /* pt_print */
|
||||
}
|
||||
|
||||
/* Initialize cli syntax */
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -431,6 +433,7 @@ cli_show_config1(clicon_handle h,
|
|||
cvec *argv)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
char *db;
|
||||
char *formatstr;
|
||||
char *xpath;
|
||||
|
|
@ -450,6 +453,10 @@ cli_show_config1(clicon_handle h,
|
|||
|
||||
goto done;
|
||||
}
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
/* First argv argument: Database */
|
||||
db = cv_string_get(cvec_i(argv, 0));
|
||||
/* Second argv argument: Format */
|
||||
|
|
@ -489,13 +496,13 @@ cli_show_config1(clicon_handle h,
|
|||
clicon_rpc_generate_error(xerr, "Get configuration", NULL);
|
||||
goto done;
|
||||
}
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
/* Some formats (eg cli) require yang */
|
||||
if ((ret = xml_spec_populate(xt, yspec, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clicon_rpc_generate_error(xerr, "Get configuration", NULL);
|
||||
goto done;
|
||||
}
|
||||
/* Some formats (eg cli) require yang */
|
||||
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
/* Print configuration according to format */
|
||||
switch (format){
|
||||
case FORMAT_XML:
|
||||
|
|
@ -681,6 +688,7 @@ cli_show_auto1(clicon_handle h,
|
|||
cvec *argv)
|
||||
{
|
||||
int retval = 1;
|
||||
int ret;
|
||||
yang_stmt *yspec;
|
||||
char *api_path_fmt; /* xml key format */
|
||||
// char *api_path = NULL; /* xml key */
|
||||
|
|
@ -740,6 +748,13 @@ cli_show_auto1(clicon_handle h,
|
|||
clicon_rpc_generate_error(xerr, "Get configuration", NULL);
|
||||
goto done;
|
||||
}
|
||||
/* Some formats (eg cli) require yang */
|
||||
if ((ret = xml_spec_populate(xt, yspec, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clicon_rpc_generate_error(xerr, "Get configuration", NULL);
|
||||
goto done;
|
||||
}
|
||||
if ((xp = xpath_first(xt, nsc, "%s", xpath)) != NULL)
|
||||
/* Print configuration according to format */
|
||||
switch (format){
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ netconf_hello_dispatch(cxobj *xn)
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Process incoming packet
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] cb Packet buffer
|
||||
|
|
@ -149,7 +148,7 @@ netconf_input_packet(clicon_handle h,
|
|||
free(str0);
|
||||
if ((xrpc=xpath_first(xreq, NULL, "//rpc")) != NULL){
|
||||
isrpc++;
|
||||
if (xml_spec_populate_rpc_input(h, xrpc, yspec) < 0)
|
||||
if (xml_spec_populate_rpc(xrpc, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_rpc(h, xrpc, &xret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -157,9 +159,11 @@ netconf_get_config(clicon_handle h,
|
|||
cxobj *xn,
|
||||
cxobj **xret)
|
||||
{
|
||||
cxobj *xfilter; /* filter */
|
||||
int retval = -1;
|
||||
cxobj *xfilter; /* filter */
|
||||
char *ftype = NULL;
|
||||
cxobj *xdata;
|
||||
yang_stmt *yspec;
|
||||
|
||||
/* ie <filter>...</filter> */
|
||||
if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL)
|
||||
|
|
@ -354,9 +358,11 @@ netconf_get(clicon_handle h,
|
|||
cxobj *xn,
|
||||
cxobj **xret)
|
||||
{
|
||||
cxobj *xfilter; /* filter */
|
||||
int retval = -1;
|
||||
cxobj *xfilter; /* filter */
|
||||
char *ftype = NULL;
|
||||
cxobj *xdata;
|
||||
yang_stmt *yspec;
|
||||
|
||||
/* ie <filter>...</filter> */
|
||||
if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL)
|
||||
|
|
@ -471,7 +477,6 @@ netconf_notification_cb(int s,
|
|||
}
|
||||
|
||||
/*
|
||||
|
||||
<create-subscription>
|
||||
<stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
|
||||
<filter type="xpath" select="XPATHEXPR"/>
|
||||
|
|
@ -586,7 +591,7 @@ netconf_application_rpc(clicon_handle h,
|
|||
/* 1. Check xn arguments with input statement. */
|
||||
if ((yinput = yang_find(yrpc, Y_INPUT, NULL)) != NULL){
|
||||
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
|
||||
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xn, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_all_top(h, xn, &xerr)) < 0)
|
||||
goto done;
|
||||
|
|
@ -618,9 +623,8 @@ netconf_application_rpc(clicon_handle h,
|
|||
if ((youtput = yang_find(yrpc, Y_OUTPUT, NULL)) != NULL){
|
||||
xoutput=xpath_first(*xret, NULL, "/");
|
||||
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
|
||||
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xoutput, yspec, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
if ((ret = xml_yang_validate_all_top(h, xoutput, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, xoutput, &xerr)) < 0)
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ You can also run restconf in a debugger.
|
|||
sudo gdb /www-data/clixon_restconf
|
||||
(gdb) run -D 1 -f /usr/local/etc/example.xml
|
||||
```
|
||||
but you need to ensure /www-data/fastcgi_restconf.sock has the following access:
|
||||
but you need to ensure /www-data/fastcgi_restconf.sock has the following access (may need to be done after restconf has started)
|
||||
```
|
||||
rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -233,7 +234,7 @@ api_root(clicon_handle h,
|
|||
|
||||
if (xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xt, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -365,7 +366,12 @@ api_data_write(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* Create a dummy data tree parent to hook in the parsed data.
|
||||
*/
|
||||
if ((xdata0 = xml_new("data0", NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xml_copy_one(api_path?xml_parent(xbot):xbot, xdata0) < 0)
|
||||
goto done;
|
||||
/* Parse input data as json or xml into xml */
|
||||
switch (media_in){
|
||||
case YANG_DATA_XML:
|
||||
|
|
@ -418,7 +424,7 @@ api_data_write(clicon_handle h,
|
|||
/* The message-body MUST contain exactly one instance of the
|
||||
* expected data resource.
|
||||
*/
|
||||
if (xml_child_nr(xdata0) != 1){
|
||||
if (xml_child_nr_type(xdata0, CX_ELMNT) != 1){
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
|
|
@ -429,7 +435,7 @@ api_data_write(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
xdata = xml_child_i(xdata0,0);
|
||||
xdata = xml_child_i_type(xdata0, 0, CX_ELMNT);
|
||||
#if 0
|
||||
if (debug){
|
||||
cbuf *ccc=cbuf_new();
|
||||
|
|
@ -481,6 +487,13 @@ api_data_write(clicon_handle h,
|
|||
xml_free(xtop);
|
||||
xtop = xdata;
|
||||
xml_name_set(xtop, "config");
|
||||
/* remove default namespace */
|
||||
if ((xa = xml_find_type(xtop, NULL, "xmlns", CX_ATTR)) != NULL){
|
||||
if (xml_rm(xa) < 0)
|
||||
goto done;
|
||||
if (xml_free(xa) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* There is an api-path that defines an element in the datastore tree.
|
||||
|
|
@ -557,7 +570,7 @@ api_data_write(clicon_handle h,
|
|||
goto done;
|
||||
nullspec = (xml_spec(xdata) == NULL);
|
||||
/* xbot is already populated, resolve yang for added xdata too */
|
||||
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate0(xdata, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if (media_in == YANG_DATA_JSON && nullspec){
|
||||
/* json2xml decode could not be done above in json_parse,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ api_data_get2(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xret, yspec, NULL) < 0)
|
||||
goto done;
|
||||
/* We get return via netconf which is complete tree from root
|
||||
* We need to cut that tree to only the object.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -108,8 +109,7 @@ api_data_post(clicon_handle h,
|
|||
{
|
||||
int retval = -1;
|
||||
enum operation_type op = OP_CREATE;
|
||||
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
|
||||
cxobj *xdata; /* -d data (without top symbol)*/
|
||||
cxobj *xdata = NULL; /* The actual data object to modify */
|
||||
int i;
|
||||
cbuf *cbx = NULL;
|
||||
cxobj *xtop = NULL; /* top of api-path */
|
||||
|
|
@ -124,10 +124,11 @@ api_data_post(clicon_handle h,
|
|||
cxobj *xretdis = NULL; /* return from discard-changes */
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
cxobj *xe; /* dont free */
|
||||
cxobj *x;
|
||||
char *username;
|
||||
int nullspec = 0;
|
||||
int ret;
|
||||
restconf_media media_in;
|
||||
int nrchildren0 = 0;
|
||||
|
||||
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path);
|
||||
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
|
||||
|
|
@ -155,15 +156,6 @@ api_data_post(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
#if 1
|
||||
if (debug){
|
||||
cbuf *ccc=cbuf_new();
|
||||
if (clicon_xml2cbuf(ccc, xtop, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s XURI:%s", __FUNCTION__, cbuf_get(ccc));
|
||||
cbuf_free(ccc);
|
||||
}
|
||||
#endif
|
||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
* expected data resource. (tested again below)
|
||||
*/
|
||||
|
|
@ -178,11 +170,19 @@ api_data_post(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
|
||||
/* Record how many children before parse (after check nr should be +1) */
|
||||
nrchildren0 = 0;
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
|
||||
nrchildren0++;
|
||||
xml_flag_set(x, XML_FLAG_MARK);
|
||||
}
|
||||
/* Parse input data as json or xml into xml */
|
||||
media_in = restconf_content_type(r);
|
||||
switch (media_in){
|
||||
case YANG_DATA_XML:
|
||||
if (xml_parse_string(data, NULL, &xdata0) < 0){
|
||||
if (xml_parse_string(data, yspec, &xbot) < 0){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
|
|
@ -195,16 +195,10 @@ api_data_post(clicon_handle h,
|
|||
}
|
||||
break;
|
||||
case YANG_DATA_JSON:
|
||||
/* Data here cannot cannot (always) be Yang populated since it is
|
||||
* loosely hanging without top symbols.
|
||||
* And if it is not yang populated, it cant be translated properly
|
||||
* from JSON to XML.
|
||||
* Therefore, yang population is done later after addsub below
|
||||
* Further complication is that if data is root resource, then it will
|
||||
* work, so I need to check below that it didnt.
|
||||
* THIS could be simplified.
|
||||
/* If xbot is top-level (api_path=null) it does not have a spec therefore look for
|
||||
* top-level (yspec) otherwise assume parent (xbot) is populated.
|
||||
*/
|
||||
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
|
||||
if ((ret = json_parse_str(data, yspec, &xbot, &xerr)) < 0){
|
||||
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
|
|
@ -230,10 +224,12 @@ api_data_post(clicon_handle h,
|
|||
goto ok;
|
||||
break;
|
||||
} /* switch media_in */
|
||||
/* 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
|
||||
/* RFC 8040 4.4.1: The message-body MUST contain exactly one instance of the
|
||||
* expected data resource.
|
||||
*/
|
||||
if (xml_child_nr(xdata0) != 1){
|
||||
clicon_debug(1, "%s nrchildren0: %d", __FUNCTION__, nrchildren0);
|
||||
if (xml_child_nr_type(xbot, CX_ELMNT) - nrchildren0 != 1){
|
||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
|
||||
|
|
@ -244,23 +240,32 @@ api_data_post(clicon_handle h,
|
|||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
xdata = xml_child_i(xdata0, 0);
|
||||
if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0)
|
||||
goto done;
|
||||
/* Find the actual (new) object, the single unmarked one */
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xbot, x, CX_ELMNT)) != NULL){
|
||||
if (xml_flag(x, XML_FLAG_MARK)){
|
||||
xml_flag_reset(x, XML_FLAG_MARK);
|
||||
continue;
|
||||
}
|
||||
xdata = x;
|
||||
}
|
||||
|
||||
/* Add operation (create/replace) as attribute */
|
||||
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
xml_prefix_set(xa, NETCONF_BASE_PREFIX);
|
||||
if (xml_value_set(xa, xml_operation2str(op)) < 0)
|
||||
goto done;
|
||||
/* Replace xbot with x, ie bottom of api-path with data */
|
||||
if (xml_addsub(xbot, xdata) < 0)
|
||||
|
||||
#if 0 /* XXX postpone this, there is something wrong with NETCONF_BASE_NAMESPACE not appearing here
|
||||
* but later it does due to default handling,... */
|
||||
if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
|
||||
goto done;
|
||||
/* xbot is already populated, resolve yang for added xdata too
|
||||
*/
|
||||
nullspec = (xml_spec(xdata) == NULL);
|
||||
if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
#else
|
||||
xml_prefix_set(xa, NETCONF_BASE_PREFIX); /* XXX: But this assumes proper namespace set */
|
||||
#endif
|
||||
|
||||
if (ys_module_by_xml(yspec, xdata, &ymoddata) < 0)
|
||||
goto done;
|
||||
/* ybot is parent of spec(parent(data))) */
|
||||
if (ymoddata && (ydata = xml_spec(xdata)) != NULL){
|
||||
|
|
@ -290,20 +295,6 @@ api_data_post(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
if (media_in == YANG_DATA_JSON && nullspec){
|
||||
/* json2xml decode may not have been done above in json_parse,
|
||||
need to be done here instead
|
||||
UNLESS it is a root resource, then json-parse has already done it
|
||||
*/
|
||||
if ((ret = json2xml_decode(xdata, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
|
||||
/* If restconf insert/point attributes are present, translate to netconf */
|
||||
if (restconf_insert_attributes(xdata, qvec) < 0)
|
||||
goto done;
|
||||
|
|
@ -405,8 +396,6 @@ api_data_post(clicon_handle h,
|
|||
xml_free(xretdis);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (xdata0)
|
||||
xml_free(xdata0);
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
return retval;
|
||||
|
|
@ -630,7 +619,7 @@ api_operations_post_output(clicon_handle h,
|
|||
if (youtput!=NULL){
|
||||
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
|
||||
#if 0
|
||||
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xoutput, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_all(xoutput, &xerr)) < 0)
|
||||
goto done;
|
||||
|
|
@ -861,7 +850,7 @@ api_operations_post(clicon_handle h,
|
|||
}
|
||||
#endif
|
||||
/* 6. Validate incoming RPC and fill in defaults */
|
||||
if (xml_spec_populate_rpc_input(h, xtop, yspec) < 0) /* */
|
||||
if (xml_spec_populate_rpc(xtop, yspec, NULL) < 0) /* */
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_rpc(h, xtop, &xret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2017-2020 Olof Hagsand
|
||||
# Copyright (C) 2017-2019 Olof Hagsand
|
||||
# Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
#
|
||||
# This file is part of CLIXON
|
||||
#
|
||||
|
|
@ -89,6 +90,11 @@ SKIPLIST="test_api.sh test_yangmodels.sh test_openconfig.sh test_install.sh test
|
|||
#IETFRFC=
|
||||
EOF
|
||||
|
||||
# Workaround for this error output:
|
||||
# sudo: setrlimit(RLIMIT_CORE): Operation not permitted
|
||||
echo "Set disable_coredump false" > /etc/sudo.conf
|
||||
|
||||
|
||||
chmod 775 /usr/local/bin/test/site.sh
|
||||
|
||||
if [ ! -d /run/nginx ]; then
|
||||
|
|
|
|||
|
|
@ -78,11 +78,15 @@ static int _state = 0;
|
|||
*/
|
||||
static char *_state_file = NULL;
|
||||
|
||||
/*! Variable to control upgrade callbacks.
|
||||
/*! Variable to control module-specific upgrade callbacks.
|
||||
* If set, call test-case for upgrading ietf-interfaces, otherwise call
|
||||
* auto-upgrade
|
||||
*/
|
||||
static int _upgrade = 0;
|
||||
static int _module_upgrade = 0;
|
||||
|
||||
/*! Variable to control general-purpose upgrade callbacks.
|
||||
*/
|
||||
static int _general_upgrade = 0;
|
||||
|
||||
/*! Variable to control transaction logging (for debug)
|
||||
* If set, call syslog for every transaction callback
|
||||
|
|
@ -326,7 +330,7 @@ example_statedata(clicon_handle h,
|
|||
clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(fd, NULL, yspec, &xstate) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xstate) < 0)
|
||||
goto done;
|
||||
}
|
||||
else {
|
||||
|
|
@ -429,7 +433,120 @@ example_extension(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Testcase upgrade function moving interfaces-state to interfaces
|
||||
/* Here follows code for general-purpose datastore upgrade
|
||||
* Nodes affected are identified by paths.
|
||||
* In this example nodes' namespaces are changed, or they are removed altogether
|
||||
* @note Order is significant, the rules are traversed in the order stated here, which means that if
|
||||
* namespaces changed, or objects are removed in one rule, you have to take that into account
|
||||
* in the next rule.
|
||||
*/
|
||||
/* Remove these paths */
|
||||
static const char *remove_map[] = {
|
||||
"/a:remove_me",
|
||||
/* add more paths to be deleted here */
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Rename the namespaces of these paths.
|
||||
* That is, paths (on the left) should get namespaces (to the right)
|
||||
*/
|
||||
static const map_str2str namespace_map[] = {
|
||||
{"/a:x/a:y/a:z/descendant-or-self::node()", "urn:example:b"},
|
||||
/* add more paths to be renamed here */
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/*! General-purpose datastore upgrade callback called once on startup
|
||||
*
|
||||
* Gets called on startup after initial XML parsing, but before module-specific upgrades
|
||||
* and before validation.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of datastore, eg "running", "startup" or "tmp"
|
||||
* @param[in] xt XML tree. Upgrade this "in place"
|
||||
* @param[in] msd Info on datastore module-state, if any
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
int
|
||||
example_upgrade(clicon_handle h,
|
||||
char *db,
|
||||
cxobj *xt,
|
||||
modstate_diff_t *msd)
|
||||
{
|
||||
int retval = -1;
|
||||
cvec *nsc = NULL; /* Canonical namespace */
|
||||
yang_stmt *yspec;
|
||||
const struct map_str2str *ms; /* map iterator */
|
||||
cxobj **xvec = NULL; /* vector of result nodes */
|
||||
size_t xlen;
|
||||
int i;
|
||||
const char **pp;
|
||||
|
||||
if (_general_upgrade == 0)
|
||||
goto ok;
|
||||
if (strcmp(db, "startup") != 0) /* skip other than startup datastore */
|
||||
goto ok;
|
||||
if (msd && msd->md_status) /* skip if there is proper module-state in datastore */
|
||||
goto ok;
|
||||
yspec = clicon_dbspec_yang(h); /* Get all yangs */
|
||||
/* Get canonical namespaces for using "normalized" prefixes */
|
||||
if (xml_nsctx_yangspec(yspec, &nsc) < 0)
|
||||
goto done;
|
||||
/* 1. Remove paths */
|
||||
for (pp = remove_map; *pp; ++pp){
|
||||
/* Find all nodes matching n */
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, *pp) < 0)
|
||||
goto done;
|
||||
/* Remove them */
|
||||
/* Loop through all nodes matching mypath and change theoir namespace */
|
||||
for (i=0; i<xlen; i++){
|
||||
if (xml_purge(xvec[i]) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (xvec){
|
||||
free(xvec);
|
||||
xvec = NULL;
|
||||
}
|
||||
}
|
||||
/* 2. Rename namespaces of the paths declared in the namespace map
|
||||
*/
|
||||
for (ms = &namespace_map[0]; ms->ms_s0; ms++){
|
||||
char *mypath;
|
||||
char *mynamespace;
|
||||
char *myprefix = NULL;
|
||||
|
||||
mypath = ms->ms_s0;
|
||||
mynamespace = ms->ms_s1;
|
||||
if (xml_nsctx_get_prefix(nsc, mynamespace, &myprefix) == 0){
|
||||
clicon_err(OE_XML, ENOENT, "Namespace %s not found in canonical namespace map",
|
||||
mynamespace);
|
||||
goto done;
|
||||
}
|
||||
/* Find all nodes matching mypath */
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, mypath) < 0)
|
||||
goto done;
|
||||
/* Loop through all nodes matching mypath and change theoir namespace */
|
||||
for (i=0; i<xlen; i++){
|
||||
/* Change namespace of this node (using myprefix) */
|
||||
if (xml_namespace_change(xvec[i], mynamespace, myprefix) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (xvec){
|
||||
free(xvec);
|
||||
xvec = NULL;
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
if (nsc)
|
||||
cvec_free(nsc);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Testcase module-specific upgrade function moving interfaces-state to interfaces
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xn XML tree to be updated
|
||||
* @param[in] ns Namespace of module (for info)
|
||||
|
|
@ -703,7 +820,8 @@ static clixon_plugin_api api = {
|
|||
.ca_trans_commit=main_commit, /* trans commit */
|
||||
.ca_trans_revert=main_revert, /* trans revert */
|
||||
.ca_trans_end=main_end, /* trans end */
|
||||
.ca_trans_abort=main_abort /* trans abort */
|
||||
.ca_trans_abort=main_abort, /* trans abort */
|
||||
.ca_datastore_upgrade=example_upgrade, /* general-purpose upgrade. */
|
||||
};
|
||||
|
||||
/*! Backend plugin initialization
|
||||
|
|
@ -728,7 +846,7 @@ clixon_plugin_init(clicon_handle h)
|
|||
goto done;
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, "rsS:ut")) != -1)
|
||||
while ((c = getopt(argc, argv, "rsS:uUt")) != -1)
|
||||
switch (c) {
|
||||
case 'r':
|
||||
_reset = 1;
|
||||
|
|
@ -739,8 +857,11 @@ clixon_plugin_init(clicon_handle h)
|
|||
case 'S': /* state file */
|
||||
_state_file = optarg;
|
||||
break;
|
||||
case 'u':
|
||||
_upgrade = 1;
|
||||
case 'u': /* module-specific upgrade */
|
||||
_module_upgrade = 1;
|
||||
break;
|
||||
case 'U': /* general-purpose upgrade */
|
||||
_general_upgrade = 1;
|
||||
break;
|
||||
case 't': /* transaction log */
|
||||
_transaction_log = 1;
|
||||
|
|
@ -798,7 +919,7 @@ clixon_plugin_init(clicon_handle h)
|
|||
/* Upgrade callback: if you start the backend with -- -u you will get the
|
||||
* test interface example. Otherwise the auto-upgrade feature is enabled.
|
||||
*/
|
||||
if (_upgrade){
|
||||
if (_module_upgrade){
|
||||
if (upgrade_callback_register(h, upgrade_2016, "urn:example:interfaces", 20140508, 20160101, NULL) < 0)
|
||||
goto done;
|
||||
if (upgrade_callback_register(h, upgrade_2018, "urn:example:interfaces", 20160101, 20180220, NULL) < 0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
|
||||
Custom file as boilerplate appended by clixon_config.h
|
||||
Note that clixon_config.h is only included by clixon system files, not automatically by examples or apps
|
||||
*/
|
||||
|
||||
#ifndef HAVE_STRNDUP
|
||||
|
|
@ -72,11 +74,20 @@
|
|||
#undef XML_EXPLICIT_INDEX
|
||||
|
||||
/*! Validate user state callback content
|
||||
* User register state callbacks using the ca_statedata callback
|
||||
* When this option is set, the XML returned from the callback is validated after merging with the
|
||||
* running db. If it fails, an internal error is returned to the originating user.
|
||||
* If the option is not set, the XML returned by the user is not validated. This could be useful if
|
||||
* there is some error in the validation, as a safety catch.
|
||||
* (Eventually this option will be permanently enabled)
|
||||
* Users may register state callbacks using ca_statedata callback
|
||||
* When this option is set, the XML returned from the callback is validated after merging with the running
|
||||
* db. If it fails, an internal error is returned to the originating user.
|
||||
* If the option is not set, the XML returned by the user is not validated.
|
||||
* Note that enabling this option causes a large performance overhead for large lists, therefore it
|
||||
* is recommended to enable it during development and debugging but disable it in production, until
|
||||
* this has been resolved.
|
||||
*/
|
||||
#define VALIDATE_STATE_XML
|
||||
|
||||
/*! Treat <config> specially in a xmldb datastore.
|
||||
* config is treated as a "neutral" tag that does not have a yang spec.
|
||||
* In particulat when binding xml to yang, if <config> is encountered as top-of-tree, do not
|
||||
* try to bind a yang-spec to this symbol.
|
||||
*/
|
||||
#define XMLDB_CONFIG_HACK
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
* JSON support functions.
|
||||
* JSON syntax is according to:
|
||||
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
* RFC 7951 JSON Encoding of Data Modeled with YANG
|
||||
*/
|
||||
#ifndef _CLIXON_JSON_H
|
||||
#define _CLIXON_JSON_H
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -85,13 +86,13 @@ int api_path2xml(char *api_path, yang_stmt *yspec, cxobj *xtop,
|
|||
cxobj **xpathp, yang_stmt **ypathp, cxobj **xerr);
|
||||
int xml2api_path_1(cxobj *x, cbuf *cb);
|
||||
#if defined(__GNUC__) && __GNUC__ >= 3
|
||||
int clixon_xml_find_api_path(cxobj *xcur, yang_stmt *yt, cxobj ***vec, size_t *veclen, char *format,
|
||||
int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, size_t *xlen, char *format,
|
||||
...) __attribute__ ((format (printf, 5, 6)));;
|
||||
int clixon_xml_find_instance_id(cxobj *xcur, yang_stmt *yt, cxobj ***vec, size_t *veclen, char *format,
|
||||
int clixon_xml_find_instance_id(cxobj *xt, yang_stmt *yt, cxobj ***xvec, size_t *xlen, char *format,
|
||||
...) __attribute__ ((format (printf, 5, 6)));;
|
||||
#else
|
||||
int clixon_xml_find_api_path(cxobj *xcur, yang_stmt *yt, cxobj ***vec, size_t *veclen, char *format,o ...);
|
||||
int clixon_xml_find_instance_id(cxobj *xcur, yang_stmt *yt, cxobj ***vec, size_t *veclen, char *format, ...);
|
||||
int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, size_t *xlen, char *format, ...);
|
||||
int clixon_xml_find_instance_id(cxobj *xt, yang_stmt *yt, cxobj ***xvec, size_t *xlen, char *format, ...);
|
||||
#endif
|
||||
|
||||
#endif /* _CLIXON_PATH_H_ */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ typedef int (plgstatedata_t)(clicon_handle h, cvec *nsc, char *xpath, cxobj *xto
|
|||
|
||||
typedef void *transaction_data;
|
||||
|
||||
/* Transaction callbacks */
|
||||
/* Transaction callback */
|
||||
typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
|
||||
|
||||
/*! Hook to override default prompt with explicit function
|
||||
|
|
@ -159,6 +160,19 @@ typedef int (trans_cb_t)(clicon_handle h, transaction_data td);
|
|||
*/
|
||||
typedef char *(cli_prompthook_t)(clicon_handle, char *mode);
|
||||
|
||||
/*! General-purpose datastore upgrade callback called once on startupo
|
||||
*
|
||||
* Gets called on startup after initial XML parsing, but before module-specific upgrades
|
||||
* and before validation.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of datastore, eg "running", "startup" or "tmp"
|
||||
* @param[in] xt XML tree. Upgrade this "in place"
|
||||
* @param[in] msd Info on datastore module-state, if any
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
typedef int (datastore_upgrade_t)(clicon_handle h, char *db, cxobj *xt, modstate_diff_t *msd);
|
||||
|
||||
/*! Startup status for use in startup-callback
|
||||
* Note that for STARTUP_ERR and _INVALID, running runs in failsafe mode
|
||||
* and startup contains the erroneous or invalid database.
|
||||
|
|
@ -206,8 +220,8 @@ struct clixon_plugin_api{
|
|||
trans_cb_t *cb_trans_revert; /* Transaction revert */
|
||||
trans_cb_t *cb_trans_end; /* Transaction completed */
|
||||
trans_cb_t *cb_trans_abort; /* Transaction aborted */
|
||||
datastore_upgrade_t *cb_datastore_upgrade; /* General-purpose datastore upgrade */
|
||||
} cau_backend;
|
||||
|
||||
} u;
|
||||
};
|
||||
/* Access fields */
|
||||
|
|
@ -224,6 +238,7 @@ struct clixon_plugin_api{
|
|||
#define ca_trans_revert u.cau_backend.cb_trans_revert
|
||||
#define ca_trans_end u.cau_backend.cb_trans_end
|
||||
#define ca_trans_abort u.cau_backend.cb_trans_abort
|
||||
#define ca_datastore_upgrade u.cau_backend.cb_datastore_upgrade
|
||||
|
||||
/*
|
||||
* Macros
|
||||
|
|
@ -271,6 +286,8 @@ int clixon_plugin_auth(clicon_handle h, void *arg);
|
|||
|
||||
int clixon_plugin_extension(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
||||
|
||||
int clixon_plugin_datastore_upgrade(clicon_handle h, char *db, cxobj *xt, modstate_diff_t *msd);
|
||||
|
||||
/* rpc callback API */
|
||||
int rpc_callback_register(clicon_handle h, clicon_rpc_cb cb, void *arg, char *namespace, char *name);
|
||||
int rpc_callback_delete_all(clicon_handle h);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
#ifndef _CLIXON_STRING_H_
|
||||
#define _CLIXON_STRING_H_
|
||||
|
||||
/* Struct used to map between int and strings. Typically used to map between
|
||||
/*! Struct used to map between int and strings. Typically used to map between
|
||||
* values and their names. Note NULL terminated
|
||||
* Example:
|
||||
* @code
|
||||
|
|
@ -55,6 +56,14 @@ struct map_str2int{
|
|||
};
|
||||
typedef struct map_str2int map_str2int;
|
||||
|
||||
/*! Struct used to map between two strings.
|
||||
*/
|
||||
struct map_str2str{
|
||||
char *ms_s0;
|
||||
char *ms_s1;
|
||||
};
|
||||
typedef struct map_str2str map_str2str;
|
||||
|
||||
/*! A malloc version that aligns on 4 bytes. To avoid warning from valgrind */
|
||||
#define align4(s) (((s)/4)*4 + 4)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -77,10 +78,19 @@ enum insert_type{ /* edit-config insert */
|
|||
INS_AFTER,
|
||||
};
|
||||
|
||||
/* XML object types */
|
||||
enum cxobj_type {CX_ERROR=-1,
|
||||
CX_ELMNT,
|
||||
CX_ATTR,
|
||||
CX_BODY};
|
||||
|
||||
/* How to bind yang to XML top-level when parsing */
|
||||
enum yang_bind{
|
||||
YB_NONE, /* Dont try to do Yang binding */
|
||||
YB_PARENT, /* Get yang binding from parents yang */
|
||||
YB_TOP, /* Get yang binding from top-level modules */
|
||||
};
|
||||
|
||||
#define CX_ANY CX_ERROR /* catch all and error is same */
|
||||
|
||||
typedef struct xml cxobj; /* struct defined in clicon_xml.c */
|
||||
|
|
@ -107,6 +117,8 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg);
|
|||
* Prototypes
|
||||
*/
|
||||
char *xml_type2str(enum cxobj_type type);
|
||||
int xml_stats_get(uint64_t *nr);
|
||||
size_t xml_size(cxobj *xt, size_t *szp);
|
||||
char *xml_name(cxobj *xn);
|
||||
int xml_name_set(cxobj *xn, char *name);
|
||||
char *xml_prefix(cxobj *xn);
|
||||
|
|
@ -115,7 +127,6 @@ char *nscache_get(cxobj *x, char *prefix);
|
|||
int nscache_get_prefix(cxobj *x, char *namespace, char **prefix);
|
||||
cvec *nscache_get_all(cxobj *x);
|
||||
int nscache_set(cxobj *x, char *prefix, char *namespace);
|
||||
|
||||
int nscache_clear(cxobj *x);
|
||||
int nscache_replace(cxobj *x, cvec *ns);
|
||||
|
||||
|
|
@ -181,8 +192,11 @@ int xml_free(cxobj *xn);
|
|||
int xml_print(FILE *f, cxobj *xn);
|
||||
int clicon_xml2file(FILE *f, cxobj *xn, int level, int prettyprint);
|
||||
int clicon_xml2cbuf(cbuf *xf, cxobj *xn, int level, int prettyprint, int32_t depth);
|
||||
int xml_parse_file(int fd, char *endtag, yang_stmt *yspec, cxobj **xt);
|
||||
int xml_parse_file(int fd, yang_stmt *yspec, cxobj **xt);
|
||||
int xml_parse_file2(int fd, enum yang_bind bind, yang_stmt *yspec, char *endtag, cxobj **xt, cxobj **xerr);
|
||||
int xml_parse_string(const char *str, yang_stmt *yspec, cxobj **xml_top);
|
||||
int xml_parse_string2(const char *str, enum yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xerr);
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 3
|
||||
int xml_parse_va(cxobj **xt, yang_stmt *yspec, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -56,11 +57,17 @@ int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1,
|
|||
cxobj ***changed_x0, cxobj ***changed_x1, size_t *changedlen);
|
||||
int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark);
|
||||
int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
|
||||
int xml_namespace_change(cxobj *x, char *namespace, char *prefix);
|
||||
int xml_default(cxobj *x, void *arg);
|
||||
int xml_sanity(cxobj *x, void *arg);
|
||||
int xml_non_config_data(cxobj *xt, void *arg);
|
||||
int xml_spec_populate_rpc_input(clicon_handle h, cxobj *x, yang_stmt *yspec);
|
||||
int xml_spec_populate(cxobj *x, void *arg);
|
||||
int xml_spec_populate_rpc(cxobj *xrpc, yang_stmt *yspec, cxobj **xerr);
|
||||
int xml_spec_populate_rpc_reply(cxobj *xrpc, char *name, yang_stmt *yspec, cxobj **xerr);
|
||||
int xml_spec_populate0(cxobj *xt, yang_stmt *yspec, cxobj **xerr);
|
||||
int xml_spec_populate0_parent(cxobj *xt, cxobj **xerr);
|
||||
int xml_spec_populate(cxobj *xt, yang_stmt *yspec, cxobj **xerr);
|
||||
int xml_spec_populate_parent(cxobj *xt, cxobj **xerr);
|
||||
|
||||
int xml2xpath(cxobj *x, char **xpath);
|
||||
int assign_namespaces(cxobj *x0, cxobj *x1, cxobj *x1p);
|
||||
int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int xml_child_spec(cxobj *x, cxobj *xp, yang_stmt *yspec, yang_stmt **yp);
|
||||
int xml_cmp(cxobj *x1, cxobj *x2, int same, int skip1);
|
||||
int xml_sort(cxobj *x0, void *arg);
|
||||
int xml_insert(cxobj *xp, cxobj *xc, enum insert_type ins, char *key_val, cvec *nsckey);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -51,6 +52,8 @@
|
|||
* This is in state of flux so it needs to be contained and easily changed.
|
||||
*/
|
||||
typedef struct {
|
||||
int md_status; /* 0 if no module-state in a datastore, 1 if there is */
|
||||
char *md_set_id; /* server-specific identifier */
|
||||
cxobj *md_del; /* yang module state deletes */
|
||||
cxobj *md_mod; /* yang module state modifications */
|
||||
} modstate_diff_t;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
#include "clixon_xml.h"
|
||||
#include "clixon_xml_sort.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_xpath_ctx.h"
|
||||
#include "clixon_xpath.h"
|
||||
|
|
|
|||
|
|
@ -69,12 +69,11 @@
|
|||
#include "clixon_file.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_datastore.h"
|
||||
|
||||
#include "clixon_datastore_write.h"
|
||||
#include "clixon_datastore_read.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -245,6 +246,7 @@ text_read_modstate(clicon_handle h,
|
|||
/* 1) There is no modules-state info in the file */
|
||||
}
|
||||
else if (xmcache && msd){
|
||||
msd->md_status = 1; /* There is module state in the file */
|
||||
/* Create diff trees */
|
||||
if (xml_parse_string("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", yspec, &msd->md_del) < 0)
|
||||
goto done;
|
||||
|
|
@ -257,6 +259,12 @@ text_read_modstate(clicon_handle h,
|
|||
|
||||
/* 3) For each module state m in the file */
|
||||
while ((xm = xml_child_each(xmodst, xm, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xm), "module-set-id") == 0){
|
||||
if (xml_body(xm) && (msd->md_set_id = strdup(xml_body(xm))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (strcmp(xml_name(xm), "module"))
|
||||
continue; /* ignore other tags, such as module-set-id */
|
||||
if ((name = xml_find_body(xm, "name")) == NULL)
|
||||
|
|
@ -342,7 +350,7 @@ xmldb_readfile(clicon_handle h,
|
|||
if ((ret = json_parse_file(fd, yspec, &x0, NULL)) < 0) /* XXX: ret == 0*/
|
||||
goto done;
|
||||
}
|
||||
else if ((xml_parse_file(fd, "</config>", yspec, &x0)) < 0)
|
||||
else if ((xml_parse_file2(fd, YB_TOP, yspec, "</config>", &x0, NULL)) < 0)
|
||||
goto done;
|
||||
|
||||
/* Always assert a top-level called "config".
|
||||
|
|
@ -548,7 +556,6 @@ xmldb_get_cache(clicon_handle h,
|
|||
/* XXX where should we apply default values once? */
|
||||
if (xml_apply(x1t, CX_ELMNT, xml_default, h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Copy the matching parts of the (relevant) XML tree.
|
||||
* If cache was empty, also update to datastore cache
|
||||
*/
|
||||
|
|
@ -671,7 +678,7 @@ xmldb_get(clicon_handle h,
|
|||
* The tree returned may be the actual cache, therefore calls for cleaning and
|
||||
* freeing tree must be made after use.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database to search in (filename including dir path
|
||||
* @param[in] db Name of datastore, eg "running"
|
||||
* @param[in] nsc External XML namespace context, or NULL
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ check_identityref(cxobj *x0,
|
|||
}
|
||||
|
||||
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
|
||||
* @param[in] th Datastore text handle
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
||||
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
|
||||
* @param[in] x0p Parent of x0
|
||||
|
|
@ -616,7 +617,7 @@ text_modify(clicon_handle h,
|
|||
} /* text_modify */
|
||||
|
||||
/*! Modify a top-level base tree x0 with modification tree x1
|
||||
* @param[in] th Datastore text handle
|
||||
* @param[in] h Clicon handle
|
||||
* @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)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
#include "clixon_err.h"
|
||||
#include "clixon_queue.h"
|
||||
#include "clixon_string.h"
|
||||
#include "clixon_log.h"
|
||||
#include "clixon_file.h"
|
||||
|
||||
/*! qsort "compar" for directory alphabetically sorting, see qsort(3)
|
||||
|
|
@ -173,12 +175,12 @@ int
|
|||
clicon_file_copy(char *src,
|
||||
char *target)
|
||||
{
|
||||
int retval = -1;
|
||||
int inF = 0, ouF = 0;
|
||||
int err = 0;
|
||||
char line[512];
|
||||
int bytes;
|
||||
struct stat st;
|
||||
int retval = -1;
|
||||
|
||||
if (stat(src, &st) != 0){
|
||||
clicon_err(OE_UNIX, errno, "stat");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
* JSON support functions.
|
||||
* JSON syntax is according to:
|
||||
* http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
* RFC 7951 JSON Encoding of Data Modeled with YANG
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
|
|
@ -1062,34 +1064,25 @@ json_xmlns_translate(yang_stmt *yspec,
|
|||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ymod;
|
||||
char *namespace0;
|
||||
char *namespace;
|
||||
char *prefix = NULL;
|
||||
char *modname = NULL;
|
||||
char *prefix;
|
||||
cxobj *xc;
|
||||
int ret;
|
||||
|
||||
prefix = xml_prefix(x); /* prefix is here module name */
|
||||
if (prefix != NULL){
|
||||
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
|
||||
if ((modname = xml_prefix(x)) != NULL){ /* prefix is here module name */
|
||||
if ((ymod = yang_find_module_by_name(yspec, modname)) == NULL){
|
||||
if (xerr &&
|
||||
netconf_unknown_namespace_xml(xerr, "application",
|
||||
prefix,
|
||||
modname,
|
||||
"No yang module found corresponding to prefix") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
namespace = yang_find_mynamespace(ymod);
|
||||
/* Get existing default namespace in tree */
|
||||
if (xml2ns(x, NULL, &namespace0) < 0)
|
||||
prefix = yang_find_myprefix(ymod);
|
||||
if (xml_namespace_change(x, namespace, prefix) < 0)
|
||||
goto done;
|
||||
/* Set xmlns="" default namespace attribute (if diff from default) */
|
||||
if (namespace0 == NULL ||
|
||||
strcmp(namespace0, namespace)){
|
||||
if (xmlns_set(x, NULL, namespace) < 0)
|
||||
goto done;
|
||||
/* and remove prefix */
|
||||
xml_prefix_set(x, NULL);
|
||||
}
|
||||
}
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL){
|
||||
|
|
@ -1112,8 +1105,8 @@ json_xmlns_translate(yang_stmt *yspec,
|
|||
* are split and interpreted as in RFC7951
|
||||
*
|
||||
* @param[in] str Input string containing JSON
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec If set, also do yang validation
|
||||
* @param[in] name Log string, typically filename
|
||||
* @param[out] xt XML top of tree typically w/o children on entry (but created)
|
||||
* @param[out] xerr Reason for invalid returned as netconf err msg
|
||||
*
|
||||
|
|
@ -1125,57 +1118,88 @@ json_xmlns_translate(yang_stmt *yspec,
|
|||
* @see RFC 7951
|
||||
*/
|
||||
static int
|
||||
json_parse(char *str,
|
||||
_json_parse(char *str,
|
||||
enum yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
const char *name,
|
||||
cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
struct clicon_json_yacc_arg jy = {0,};
|
||||
clixon_json_yacc jy = {0,};
|
||||
int ret;
|
||||
cxobj *x;
|
||||
cbuf *cberr = NULL;
|
||||
int i;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, str);
|
||||
jy.jy_parse_string = str;
|
||||
jy.jy_name = name;
|
||||
jy.jy_linenum = 1;
|
||||
jy.jy_current = xt;
|
||||
jy.jy_xtop = xt;
|
||||
if (json_scan_init(&jy) < 0)
|
||||
goto done;
|
||||
if (json_parse_init(&jy) < 0)
|
||||
goto done;
|
||||
if (clixon_json_parseparse(&jy) != 0) { /* yacc returns 1 on error */
|
||||
clicon_log(LOG_NOTICE, "JSON error: %s on line %d", name, jy.jy_linenum);
|
||||
clicon_log(LOG_NOTICE, "JSON error: line %d", jy.jy_linenum);
|
||||
if (clicon_errno == 0)
|
||||
clicon_err(OE_XML, 0, "JSON parser error with no error code (should not happen)");
|
||||
goto done;
|
||||
}
|
||||
if (yspec){
|
||||
/* Traverse new objects */
|
||||
for (i = 0; i < jy.jy_xlen; i++) {
|
||||
x = jy.jy_xvec[i];
|
||||
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
|
||||
* members of a top-level JSON object
|
||||
*/
|
||||
if (yspec && xml_prefix(x) == NULL){
|
||||
if ((cberr = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x));
|
||||
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* Names are split into name/prefix, but now add namespace info */
|
||||
if ((ret = json_xmlns_translate(yspec, xt, xerr)) < 0)
|
||||
if ((ret = json_xmlns_translate(yspec, x, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Populate, ie associate xml nodes with yang specs.
|
||||
* XXX But this wrong if xt is not on top-level, can give false positives.
|
||||
/* Now assign yang stmts to each XML node
|
||||
* XXX should be xml_spec_populate0_parent() sometimes.
|
||||
*/
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0){
|
||||
switch (yb){
|
||||
case YB_NONE:
|
||||
break;
|
||||
case YB_PARENT:
|
||||
if (xml_spec_populate0_parent(x, NULL) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case YB_TOP:
|
||||
if (xml_spec_populate0(x, yspec, NULL) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
|
||||
goto done;
|
||||
/* Now find leafs with identityrefs (+transitive) and translate
|
||||
* prefixes in values to XML namespaces */
|
||||
if ((ret = json2xml_decode(xt, xerr)) < 0)
|
||||
if ((ret = json2xml_decode(x, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0) /* XXX necessary? */
|
||||
goto fail;
|
||||
}
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
|
||||
goto done;
|
||||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (cberr)
|
||||
cbuf_free(cberr);
|
||||
json_parse_exit(&jy);
|
||||
json_scan_exit(&jy);
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (jy.jy_xvec)
|
||||
free(jy.jy_xvec);
|
||||
return retval;
|
||||
fail: /* invalid */
|
||||
retval = 0;
|
||||
|
|
@ -1185,7 +1209,7 @@ json_parse(char *str,
|
|||
/*! Parse string containing JSON and return an XML tree
|
||||
*
|
||||
* @param[in] str String containing JSON
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in] yspec Yang specification, mandatory to make module->xmlns translation
|
||||
* @param[in,out] xt Top object, if not exists, on success it is created with name 'top'
|
||||
* @param[out] xerr Reason for invalid returned as netconf err msg
|
||||
*
|
||||
|
|
@ -1208,11 +1232,23 @@ json_parse_str(char *str,
|
|||
cxobj **xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
enum yang_bind yb = YB_PARENT;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if (*xt == NULL)
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (*xt == NULL){
|
||||
yb = YB_TOP; /* ad-hoc #1 */
|
||||
if ((*xt = xml_new("top", NULL, NULL)) == NULL)
|
||||
return -1;
|
||||
return json_parse(str, yspec, "", *xt, xerr);
|
||||
}
|
||||
else{
|
||||
if (xml_spec(*xt) == NULL)
|
||||
yb = YB_TOP; /* ad-hoc #2 */
|
||||
}
|
||||
return _json_parse(str, yb, yspec, *xt, xerr);
|
||||
}
|
||||
|
||||
/*! Read a JSON definition from file and parse it into a parse-tree.
|
||||
|
|
@ -1263,7 +1299,14 @@ json_parse_file(int fd,
|
|||
char *ptr;
|
||||
char ch;
|
||||
int len = 0;
|
||||
enum yang_bind yb = YB_PARENT;
|
||||
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (*xt==NULL)
|
||||
yb = YB_TOP;
|
||||
if ((jsonbuf = malloc(jsonbuflen)) == NULL){
|
||||
clicon_err(OE_XML, errno, "malloc");
|
||||
goto done;
|
||||
|
|
@ -1282,7 +1325,7 @@ json_parse_file(int fd,
|
|||
if ((*xt = xml_new(JSON_TOP_SYMBOL, NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
if (len){
|
||||
if ((ret = json_parse(ptr, yspec, "", *xt, xerr)) < 0)
|
||||
if ((ret = _json_parse(ptr, yb, yspec, *xt, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -39,13 +40,16 @@
|
|||
* Types
|
||||
*/
|
||||
|
||||
struct clicon_json_yacc_arg{ /* XXX: mostly unrelevant */
|
||||
const char *jy_name; /* Name of syntax (for error string) */
|
||||
struct clixon_json_yacc_arg{ /* XXX: mostly unrelevant */
|
||||
int jy_linenum; /* Number of \n in parsed buffer */
|
||||
char *jy_parse_string; /* original (copy of) parse string */
|
||||
void *jy_lexbuf; /* internal parse buffer from lex */
|
||||
cxobj *jy_current;
|
||||
cxobj *jy_xtop; /* cxobj top element (fixed) */
|
||||
cxobj *jy_current; /* cxobj active element (changes with parse context) */
|
||||
cxobj **jy_xvec; /* Vector of created top-level nodes (to know which are created) */
|
||||
size_t jy_xlen; /* Length of jy_xvec */
|
||||
};
|
||||
typedef struct clixon_json_yacc_arg clixon_json_yacc;
|
||||
|
||||
/*
|
||||
* Variables
|
||||
|
|
@ -55,11 +59,11 @@ extern char *clixon_json_parsetext;
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int json_scan_init(struct clicon_json_yacc_arg *jy);
|
||||
int json_scan_exit(struct clicon_json_yacc_arg *jy);
|
||||
int json_scan_init(clixon_json_yacc *jy);
|
||||
int json_scan_exit(clixon_json_yacc *jy);
|
||||
|
||||
int json_parse_init(struct clicon_json_yacc_arg *jy);
|
||||
int json_parse_exit(struct clicon_json_yacc_arg *jy);
|
||||
int json_parse_init(clixon_json_yacc *jy);
|
||||
int json_parse_exit(clixon_json_yacc *jy);
|
||||
|
||||
int clixon_json_parselex(void *);
|
||||
int clixon_json_parseparse(void *);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
#define YY_NO_INPUT
|
||||
|
||||
/* typecast macro */
|
||||
#define _JY ((struct clicon_json_yacc_arg *)_yy)
|
||||
#define _JY ((clixon_json_yacc *)_yy)
|
||||
|
||||
#define MAXBUF 4*4*64*1024
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ exp ({integer}|{real})[eE][+-]{integer}
|
|||
/*! Initialize scanner.
|
||||
*/
|
||||
int
|
||||
json_scan_init(struct clicon_json_yacc_arg *jy)
|
||||
json_scan_init(clixon_json_yacc *jy)
|
||||
{
|
||||
BEGIN(START);
|
||||
jy->jy_lexbuf = yy_scan_string (jy->jy_parse_string);
|
||||
|
|
@ -136,7 +137,7 @@ json_scan_init(struct clicon_json_yacc_arg *jy)
|
|||
* Even within Flex version 2.5 (this is assumed), freeing buffers is different.
|
||||
*/
|
||||
int
|
||||
json_scan_exit(struct clicon_json_yacc_arg *jy)
|
||||
json_scan_exit(clixon_json_yacc *jy)
|
||||
{
|
||||
yy_delete_buffer(jy->jy_lexbuf);
|
||||
clixon_json_parselex_destroy(); /* modern */
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -99,7 +100,7 @@ object.
|
|||
/* Here starts user C-code */
|
||||
|
||||
/* typecast macro */
|
||||
#define _JY ((struct clicon_json_yacc_arg *)_jy)
|
||||
#define _JY ((clixon_json_yacc *)_jy)
|
||||
|
||||
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;}
|
||||
|
||||
|
|
@ -142,8 +143,7 @@ void
|
|||
clixon_json_parseerror(void *_jy,
|
||||
char *s)
|
||||
{
|
||||
clicon_err(OE_XML, 0, "%s on line %d: %s at or before: '%s'",
|
||||
_JY->jy_name,
|
||||
clicon_err(OE_XML, XMLPARSE_ERRNO, "json_parse: line %d: %s at or before: '%s'",
|
||||
_JY->jy_linenum ,
|
||||
s,
|
||||
clixon_json_parsetext);
|
||||
|
|
@ -151,14 +151,14 @@ clixon_json_parseerror(void *_jy,
|
|||
}
|
||||
|
||||
int
|
||||
json_parse_init(struct clicon_json_yacc_arg *jy)
|
||||
json_parse_init(clixon_json_yacc *jy)
|
||||
{
|
||||
// clicon_debug_init(2, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
json_parse_exit(struct clicon_json_yacc_arg *jy)
|
||||
json_parse_exit(clixon_json_yacc *jy)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ json_parse_exit(struct clicon_json_yacc_arg *jy)
|
|||
* Split name into prefix:name (extended JSON RFC7951)
|
||||
*/
|
||||
static int
|
||||
json_current_new(struct clicon_json_yacc_arg *jy,
|
||||
json_current_new(clixon_json_yacc *jy,
|
||||
char *name)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -183,6 +183,11 @@ json_current_new(struct clicon_json_yacc_arg *jy,
|
|||
goto done;
|
||||
if (prefix && xml_prefix_set(x, prefix) < 0)
|
||||
goto done;
|
||||
/* If topmost, add to top-list created list */
|
||||
if (jy->jy_current == jy->jy_xtop){
|
||||
if (cxvec_append(x, &jy->jy_xvec, &jy->jy_xlen) < 0)
|
||||
goto done;
|
||||
}
|
||||
jy->jy_current = x;
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -194,7 +199,7 @@ json_current_new(struct clicon_json_yacc_arg *jy,
|
|||
}
|
||||
|
||||
static int
|
||||
json_current_pop(struct clicon_json_yacc_arg *jy)
|
||||
json_current_pop(clixon_json_yacc *jy)
|
||||
{
|
||||
if (jy->jy_current)
|
||||
jy->jy_current = xml_parent(jy->jy_current);
|
||||
|
|
@ -202,7 +207,7 @@ json_current_pop(struct clicon_json_yacc_arg *jy)
|
|||
}
|
||||
|
||||
static int
|
||||
json_current_clone(struct clicon_json_yacc_arg *jy)
|
||||
json_current_clone(clixon_json_yacc *jy)
|
||||
{
|
||||
cxobj *xn;
|
||||
|
||||
|
|
@ -217,7 +222,7 @@ json_current_clone(struct clicon_json_yacc_arg *jy)
|
|||
}
|
||||
|
||||
static int
|
||||
json_current_body(struct clicon_json_yacc_arg *jy,
|
||||
json_current_body(clixon_json_yacc *jy,
|
||||
char *value)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -240,7 +241,7 @@ parse_configfile(clicon_handle h,
|
|||
}
|
||||
clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename);
|
||||
fd = fileno(f);
|
||||
if (xml_parse_file(fd, "</clicon>", yspec, &xt) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xt) < 0)
|
||||
goto done;
|
||||
if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){
|
||||
clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -1484,8 +1485,8 @@ clixon_path_search(cxobj *xt,
|
|||
* @code
|
||||
* cxobj **xvec = NULL;
|
||||
* size_t xlen;
|
||||
* if (clixon_xml_find_api_path(x, &xvec, &xlen, "/symbol/%s", "foo") < 0)
|
||||
* goto err;
|
||||
* if (clixon_xml_find_api_path(x, yspec, &xvec, &xlen, "/symbol/%s", "foo") < 0)
|
||||
* err;
|
||||
* for (i=0; i<xlen; i++){
|
||||
* xn = xvec[i];
|
||||
* ...
|
||||
|
|
@ -1547,9 +1548,9 @@ clixon_xml_find_api_path(cxobj *xt,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*! Given (instance-id) path and an XML tree, return matching xml node vector using stdarg
|
||||
/*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
|
||||
*
|
||||
* Instance-identifier is a subset of XML Xpaths and defined in Yang, used in NACM for example.
|
||||
* Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for example.
|
||||
* @param[in] xt Top xml-tree where to search
|
||||
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
|
||||
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
|
||||
|
|
@ -1573,7 +1574,7 @@ clixon_xml_find_api_path(cxobj *xt,
|
|||
* }
|
||||
* free(xvec);
|
||||
* @endcode
|
||||
* @see xpath_vec for full XML Xpaths
|
||||
* @see xpath_vec for full XML XPaths
|
||||
* @see api_path_search for RESTCONF api-paths
|
||||
* @see RFC7950 Sec 9.13
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -59,6 +61,7 @@
|
|||
#include "clixon_handle.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
|
||||
/* List of plugins XXX
|
||||
|
|
@ -361,7 +364,7 @@ clixon_plugin_start(clicon_handle h)
|
|||
if ((startfn = cp->cp_api.ca_start) == NULL)
|
||||
continue;
|
||||
if (startfn(h) < 0) {
|
||||
clicon_debug(1, "plugin_start() failed");
|
||||
clicon_debug(1, "%s() failed", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -384,7 +387,7 @@ clixon_plugin_exit(clicon_handle h)
|
|||
if ((exitfn = cp->cp_api.ca_exit) == NULL)
|
||||
continue;
|
||||
if (exitfn(h) < 0) {
|
||||
clicon_debug(1, "plugin_exit() failed");
|
||||
clicon_debug(1, "%s() failed", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
if (dlclose(cp->cp_handle) != 0) {
|
||||
|
|
@ -425,7 +428,7 @@ clixon_plugin_auth(clicon_handle h,
|
|||
if ((authfn = cp->cp_api.ca_auth) == NULL)
|
||||
continue;
|
||||
if ((retval = authfn(h, arg)) < 0) {
|
||||
clicon_debug(1, "plugin_auth() failed");
|
||||
clicon_debug(1, "%s() failed", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
|
@ -460,13 +463,48 @@ clixon_plugin_extension(clicon_handle h,
|
|||
if ((extfn = cp->cp_api.ca_extension) == NULL)
|
||||
continue;
|
||||
if ((retval = extfn(h, yext, ys)) < 0) {
|
||||
clicon_debug(1, "plugin_extension() failed");
|
||||
clicon_debug(1, "%s() failed", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Call plugingeneral-purpose datastore upgrade in all plugins
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of datastore, eg "running", "startup" or "tmp"
|
||||
* @param[in] xt XML tree. Upgrade this "in place"
|
||||
* @param[in] msd Module-state diff, info on datastore module-state
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
* Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
|
||||
* @param[in] h Clicon handle
|
||||
* Call plugin start functions (if defined)
|
||||
* @note Start functions used to have argc/argv. Use clicon_argv_get() instead
|
||||
*/
|
||||
int
|
||||
clixon_plugin_datastore_upgrade(clicon_handle h,
|
||||
char *db,
|
||||
cxobj *xt,
|
||||
modstate_diff_t *msd)
|
||||
{
|
||||
clixon_plugin *cp;
|
||||
int i;
|
||||
datastore_upgrade_t *repairfn;
|
||||
|
||||
for (i = 0; i < _clixon_nplugins; i++) {
|
||||
cp = &_clixon_plugins[i];
|
||||
if ((repairfn = cp->cp_api.ca_datastore_upgrade) == NULL)
|
||||
continue;
|
||||
if (repairfn(h, db, xt, msd) < 0) {
|
||||
clicon_debug(1, "%s() failed", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------
|
||||
* RPC callbacks for both client/frontend and backend plugins.
|
||||
* RPC callbacks are explicitly registered in the plugin_init() function
|
||||
|
|
@ -715,7 +753,7 @@ upgrade_callback_call(clicon_handle h,
|
|||
int ret;
|
||||
|
||||
if (upgrade_cb_list == NULL)
|
||||
return 0;
|
||||
return 1;
|
||||
uc = upgrade_cb_list;
|
||||
do {
|
||||
/* For matching an upgrade callback:
|
||||
|
|
@ -753,3 +791,4 @@ upgrade_callback_call(clicon_handle h,
|
|||
retval =0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -65,6 +66,7 @@
|
|||
#include "clixon_xml.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_string.h"
|
||||
#include "clixon_xpath_ctx.h"
|
||||
|
|
@ -73,13 +75,15 @@
|
|||
#include "clixon_err.h"
|
||||
#include "clixon_err_string.h"
|
||||
#include "clixon_xml_nsctx.h"
|
||||
#include "clixon_xml_map.h"
|
||||
#include "clixon_xml_sort.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_proto_client.h"
|
||||
|
||||
/*! Send internal netconf rpc from client to backend
|
||||
* @param[in] h CLICON handle
|
||||
* @param[in] msg Encoded message. Deallocate woth free
|
||||
* @param[out] xret Return value from backend as xml tree. Free w xml_free
|
||||
* @param[out] xret0 Return value from backend as xml tree. Free w xml_free
|
||||
* @param[inout] sock0 If pointer exists, do not close socket to backend on success
|
||||
* and return it here. For keeping a notify socket open
|
||||
* @note sock0 is if connection should be persistent, like a notification/subscribe api
|
||||
|
|
@ -96,7 +100,6 @@ clicon_rpc_msg(clicon_handle h,
|
|||
int port;
|
||||
char *retdata = NULL;
|
||||
cxobj *xret = NULL;
|
||||
yang_stmt *yspec;
|
||||
|
||||
#ifdef RPC_USERNAME_ASSERT
|
||||
assert(strstr(msg->op_body, "username")!=NULL); /* XXX */
|
||||
|
|
@ -135,8 +138,10 @@ clicon_rpc_msg(clicon_handle h,
|
|||
clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata);
|
||||
|
||||
if (retdata){
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if (xml_parse_string(retdata, yspec, &xret) < 0)
|
||||
/* Cannot populate xret here because need to know RPC name (eg "lock") in order to associate yang
|
||||
* to reply.
|
||||
*/
|
||||
if (xml_parse_string2(retdata, YB_NONE, NULL, &xret, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (xret0){
|
||||
|
|
@ -241,15 +246,31 @@ clicon_rpc_netconf_xml(clicon_handle h,
|
|||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
cxobj *xname;
|
||||
char *rpcname;
|
||||
cxobj *xreply;
|
||||
yang_stmt *yspec;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((xname = xml_child_i_type(xml, 0, 0)) == NULL){
|
||||
clicon_err(OE_NETCONF, EINVAL, "Missing rpc name");
|
||||
goto done;
|
||||
}
|
||||
rpcname = xml_name(xname); /* Store rpc name and use in yang binding after reply */
|
||||
if (clicon_xml2cbuf(cb, xml, 0, 0, -1) < 0)
|
||||
goto done;
|
||||
if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
|
||||
goto done;
|
||||
if ((xreply = xml_find_type(*xret, NULL, "rpc-reply", CX_ELMNT)) != NULL &&
|
||||
xml_find_type(xreply, NULL, "rpc-error", CX_ELMNT) == NULL){
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
/* Here use rpc name to bind to yang */
|
||||
if (xml_spec_populate_rpc_reply(xreply, rpcname, yspec, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
|
|
@ -318,7 +339,9 @@ clicon_rpc_generate_error(cxobj *xerr,
|
|||
* if (nsc)
|
||||
* xml_nsctx_free(nsc);
|
||||
* @endcode
|
||||
* @see clicon_rpc_get
|
||||
* @see clicon_rpc_generate_error
|
||||
* @note the netconf return message us yang populated, but returned data is not
|
||||
*/
|
||||
int
|
||||
clicon_rpc_get_config(clicon_handle h,
|
||||
|
|
@ -388,7 +411,6 @@ clicon_rpc_get_config(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Send database entries as XML to backend daemon
|
||||
* @param[in] h CLICON handle
|
||||
* @param[in] db Name of database
|
||||
|
|
@ -650,6 +672,7 @@ clicon_rpc_unlock(clicon_handle h,
|
|||
* @endcode
|
||||
* @see clicon_rpc_get_config which is almost the same as with content=config, but you can also select dbname
|
||||
* @see clicon_rpc_generate_error
|
||||
* @note the netconf return message us yang populated, but returned data is not
|
||||
*/
|
||||
int
|
||||
clicon_rpc_get(clicon_handle h,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -233,10 +234,10 @@ validate_identityref(cxobj *xt,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
#if 0 /* Assume proper namespace, otherwise we assume module prefixes,
|
||||
/* Assume proper namespace, otherwise we assume module prefixes,
|
||||
* see IDENTITYREF_KLUDGE
|
||||
*/
|
||||
{
|
||||
if (0){
|
||||
char *namespace;
|
||||
yang_stmt *ymod;
|
||||
yang_stmt *yspec;
|
||||
|
|
@ -255,7 +256,7 @@ validate_identityref(cxobj *xt,
|
|||
}
|
||||
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
|
||||
}
|
||||
#else
|
||||
#if 1
|
||||
{
|
||||
yang_stmt *ymod;
|
||||
/* idref from prefix:id to module:id */
|
||||
|
|
@ -1071,6 +1072,7 @@ xml_yang_validate_all(clicon_handle h,
|
|||
int nr;
|
||||
int ret;
|
||||
cxobj *x;
|
||||
cxobj *xp;
|
||||
char *namespace = NULL;
|
||||
cbuf *cb = NULL;
|
||||
cvec *nsc = NULL;
|
||||
|
|
@ -1085,13 +1087,16 @@ xml_yang_validate_all(clicon_handle h,
|
|||
}
|
||||
if (xml2ns(xt, xml_prefix(xt), &namespace) < 0)
|
||||
goto done;
|
||||
cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt));
|
||||
if ((xp = xml_parent(xt)) != NULL)
|
||||
cprintf(cb, " with parent: %s", xml_name(xp));
|
||||
if (namespace){
|
||||
cprintf(cb, "namespace is: %s", namespace);
|
||||
cprintf(cb, " in namespace: %s", namespace);
|
||||
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
}
|
||||
else
|
||||
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), NULL) < 0)
|
||||
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -45,11 +46,12 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* cligen */
|
||||
|
|
@ -165,6 +167,72 @@ xml_type2str(enum cxobj_type type)
|
|||
return (char*)clicon_int2str(xsmap, type);
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
uint64_t _stats_nr = 0;
|
||||
|
||||
/*! Get global statistics about XML objects
|
||||
*/
|
||||
int
|
||||
xml_stats_get(uint64_t *nr)
|
||||
{
|
||||
if (nr)
|
||||
*nr = _stats_nr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Return the alloced memory of a single XML obj
|
||||
* @param[in] x XML object
|
||||
* @param[out] szp Size of this XML obj
|
||||
* @retval 0 OK
|
||||
* (baseline: 96 bytes per object on x86-64)
|
||||
*/
|
||||
static int
|
||||
xml_size_one(cxobj *x,
|
||||
size_t *szp)
|
||||
{
|
||||
size_t sz = 0;
|
||||
|
||||
sz += sizeof(struct xml);
|
||||
if (x->x_name)
|
||||
sz += strlen(x->x_name) + 1;
|
||||
if (x->x_prefix)
|
||||
sz += strlen(x->x_prefix) + 1;
|
||||
sz += x->x_childvec_max*sizeof(struct xml*);
|
||||
if (x->x_value_cb)
|
||||
sz += cbuf_buflen(x->x_value_cb);
|
||||
if (x->x_cv)
|
||||
sz += cv_size(x->x_cv);
|
||||
if (x->x_ns_cache)
|
||||
sz += cvec_size(x->x_ns_cache);
|
||||
if (szp)
|
||||
*szp = sz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Return the alloced memory of a XML obj tree recursively
|
||||
* @param[in] x XML object
|
||||
* @param[out] szp Size of this XML obj recursively
|
||||
* @retval 0 OK
|
||||
*/
|
||||
size_t
|
||||
xml_size(cxobj *xt,
|
||||
size_t *szp)
|
||||
{
|
||||
size_t sz = 0;
|
||||
cxobj *xc;
|
||||
|
||||
xml_size_one(xt, &sz);
|
||||
if (szp)
|
||||
*szp += sz;
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(xt, xc, -1)) != NULL) {
|
||||
xml_size(xc, &sz);
|
||||
if (szp)
|
||||
*szp += sz;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Access functions
|
||||
*/
|
||||
|
|
@ -353,6 +421,10 @@ nscache_clear(cxobj *x)
|
|||
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
* if (xml2ns(xt, NULL, &namespace) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
* @see xmlns_check
|
||||
* @see xmlns_set cache is set
|
||||
* @note, this function uses a cache.
|
||||
|
|
@ -444,7 +516,7 @@ xmlns_set(cxobj *x,
|
|||
* @param[out] prefixp Pointer to prefix if found
|
||||
* @retval -1 Error
|
||||
* @retval 0 No namespace found
|
||||
* @retval 1 Namespace found
|
||||
* @retval 1 Namespace found, prefix returned in prefixp
|
||||
*/
|
||||
int
|
||||
xml2prefix(cxobj *xn,
|
||||
|
|
@ -455,6 +527,7 @@ xml2prefix(cxobj *xn,
|
|||
cxobj *xa = NULL;
|
||||
cxobj *xp;
|
||||
char *prefix = NULL;
|
||||
char *xaprefix;
|
||||
int ret;
|
||||
|
||||
if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
|
||||
|
|
@ -471,7 +544,8 @@ xml2prefix(cxobj *xn,
|
|||
}
|
||||
}
|
||||
/* xmlns:prefix=namespace */
|
||||
else if (strcmp("xmlns", xml_prefix(xa)) == 0){
|
||||
else if ((xaprefix=xml_prefix(xa)) != NULL &&
|
||||
strcmp("xmlns", xaprefix) == 0){
|
||||
if (strcmp(xml_value(xa), namespace) == 0){
|
||||
prefix = xml_name(xa);
|
||||
if (nscache_set(xn, prefix, namespace) < 0)
|
||||
|
|
@ -837,10 +911,6 @@ xml_child_order(cxobj *xp,
|
|||
}
|
||||
|
||||
/*! Iterator over xml children objects
|
||||
*
|
||||
* @note Never manipulate the child-list during operation or using the
|
||||
* same object recursively, the function uses an internal field to remember the
|
||||
* index used. It works as long as the same object is not iterated concurrently.
|
||||
*
|
||||
* @param[in] xparent xml tree node whose children should be iterated
|
||||
* @param[in] xprev previous child, or NULL on init
|
||||
|
|
@ -852,6 +922,9 @@ xml_child_order(cxobj *xp,
|
|||
* }
|
||||
* @endcode
|
||||
* @note makes uses _x_vector_i:can be changed if list changed between calls
|
||||
* @note Never manipulate the child-list during operation or using the
|
||||
* same object recursively, the function uses an internal field to remember the
|
||||
* index used. It works as long as the same object is not iterated concurrently.
|
||||
*/
|
||||
cxobj *
|
||||
xml_child_each(cxobj *xparent,
|
||||
|
|
@ -867,7 +940,7 @@ xml_child_each(cxobj *xparent,
|
|||
xn = xparent->x_childvec[i];
|
||||
if (xn == NULL)
|
||||
continue;
|
||||
if (type != CX_ERROR && xn->x_type != type)
|
||||
if (type != CX_ERROR && xml_type(xn) != type)
|
||||
continue;
|
||||
break; /* this is next object after previous */
|
||||
}
|
||||
|
|
@ -994,6 +1067,7 @@ xml_new(char *name,
|
|||
x->_x_i = xml_child_nr(xp)-1;
|
||||
}
|
||||
x->x_spec = yspec; /* Can be NULL */
|
||||
_stats_nr++;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
|
@ -1236,9 +1310,8 @@ xml_child_rm(cxobj *xp,
|
|||
xp->x_childvec[i] = NULL;
|
||||
xml_parent_set(xc, NULL);
|
||||
xp->x_childvec_len--;
|
||||
/* shift up, note same index i used but ok since we break */
|
||||
for (; i<xp->x_childvec_len; i++)
|
||||
xp->x_childvec[i] = xp->x_childvec[i+1];
|
||||
if (i<xp->x_childvec_len)
|
||||
memmove(&xp->x_childvec[i], &xp->x_childvec[i+1], (xp->x_childvec_len-i)*sizeof(cxobj*));
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -1655,6 +1728,7 @@ xml_free(cxobj *x)
|
|||
if (x->x_ns_cache)
|
||||
xml_nsctx_free(x->x_ns_cache);
|
||||
free(x);
|
||||
_stats_nr--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1717,7 +1791,7 @@ clicon_xml2file(FILE *f,
|
|||
xc = NULL;
|
||||
/* print attributes only */
|
||||
while ((xc = xml_child_each(x, xc, -1)) != NULL) {
|
||||
switch (xc->x_type){
|
||||
switch (xml_type(xc)){
|
||||
case CX_ATTR:
|
||||
if (clicon_xml2file(f, xc, level+1, prettyprint) <0)
|
||||
goto done;
|
||||
|
|
@ -1846,7 +1920,7 @@ clicon_xml2cbuf(cbuf *cb,
|
|||
xc = NULL;
|
||||
/* print attributes only */
|
||||
while ((xc = xml_child_each(x, xc, -1)) != NULL)
|
||||
switch (xc->x_type){
|
||||
switch (xml_type(xc)){
|
||||
case CX_ATTR:
|
||||
if (clicon_xml2cbuf(cb, xc, level+1, prettyprint, -1) < 0)
|
||||
goto done;
|
||||
|
|
@ -1938,22 +2012,34 @@ xmltree2cbuf(cbuf *cb,
|
|||
*
|
||||
* Given a string containing XML, parse into existing XML tree and return
|
||||
* @param[in] str Pointer to string containing XML definition.
|
||||
* @param[in] yspec Yang specification or NULL
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
|
||||
* @param[in,out] xtop Top of XML parse tree. Assume created. Holds new tree.
|
||||
* @param[out] xerr Reason for failure (yang assignment not made)
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
|
||||
* @retval -1 Error with clicon_err called. Includes parse error
|
||||
* @see xml_parse_file
|
||||
* @see xml_parse_string
|
||||
* @see xml_parse_va
|
||||
* @see _json_parse
|
||||
* @note special case is empty XML where the parser is not invoked.
|
||||
*/
|
||||
static int
|
||||
_xml_parse(const char *str,
|
||||
enum yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj *xt)
|
||||
cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
struct xml_parse_yacc_arg ya = {0,};
|
||||
clixon_xml_yacc ya = {0,};
|
||||
cxobj *x;
|
||||
int ret;
|
||||
int failed = 0; /* yang assignment */
|
||||
int i;
|
||||
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, str);
|
||||
if (strlen(str) == 0)
|
||||
return 0; /* OK */
|
||||
if (xt == NULL){
|
||||
|
|
@ -1964,35 +2050,109 @@ _xml_parse(const char *str,
|
|||
clicon_err(OE_XML, errno, "strdup");
|
||||
return -1;
|
||||
}
|
||||
ya.ya_xtop = xt;
|
||||
ya.ya_xparent = xt;
|
||||
ya.ya_yspec = yspec;
|
||||
if (clixon_xml_parsel_init(&ya) < 0)
|
||||
goto done;
|
||||
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
|
||||
goto done;
|
||||
/* Purge all top-level body objects */
|
||||
x = NULL;
|
||||
while ((x = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
|
||||
xml_purge(x);
|
||||
/* Traverse new objects */
|
||||
for (i = 0; i < ya.ya_xlen; i++) {
|
||||
x = ya.ya_xvec[i];
|
||||
/* Verify namespaces after parsing */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0)
|
||||
if (xml_apply0(x, CX_ELMNT, xml_localname_check, NULL) < 0)
|
||||
goto done;
|
||||
/* Sort the complete tree after parsing */
|
||||
if (yspec){
|
||||
/* Populate, ie associate xml nodes with yang specs */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
/* Populate, ie associate xml nodes with yang specs
|
||||
*/
|
||||
switch (yb){
|
||||
case YB_NONE:
|
||||
break;
|
||||
case YB_PARENT:
|
||||
/* xt:n Has spec
|
||||
* x: <a> <-- populate from parent
|
||||
*/
|
||||
if ((ret = xml_spec_populate0_parent(x, xerr)) < 0)
|
||||
goto done;
|
||||
/* Sort according to yang */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
case YB_TOP:
|
||||
/* xt:<top> nospec
|
||||
* x: <a> <-- populate from modules
|
||||
*/
|
||||
#ifdef XMLDB_CONFIG_HACK
|
||||
if (strcmp(xml_name(x),"config") == 0){
|
||||
/* xt:<top> nospec
|
||||
* x: <config>
|
||||
* <a> <-- populate from modules
|
||||
*/
|
||||
if ((ret = xml_spec_populate(x, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
else
|
||||
#endif
|
||||
if ((ret = xml_spec_populate0(x, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Sort the complete tree after parsing. Sorting is less meaningful if Yang not bound */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0)
|
||||
goto done;
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
clixon_xml_parsel_exit(&ya);
|
||||
if (ya.ya_parse_string != NULL)
|
||||
free(ya.ya_parse_string);
|
||||
if (ya.ya_xvec)
|
||||
free(ya.ya_xvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Read an XML definition from file and parse it into a parse-tree.
|
||||
*
|
||||
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial)
|
||||
* @retval -1 Error with clicon_err called. Includes parse error *
|
||||
* @code
|
||||
* cxobj *xt = NULL;
|
||||
* int fd;
|
||||
* fd = open(filename, O_RDONLY);
|
||||
* xml_parse_file(fd, yspec, &xt);
|
||||
* xml_free(xt);
|
||||
* @endcode
|
||||
* @see xml_parse_string
|
||||
* @see xml_parse_va
|
||||
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
|
||||
* @note May block on file I/O
|
||||
* @see xml_parse_file2 for a more advanced API
|
||||
*/
|
||||
int
|
||||
xml_parse_file(int fd,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xt)
|
||||
{
|
||||
enum yang_bind yb = YB_PARENT;
|
||||
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (*xt==NULL)
|
||||
yb = YB_TOP;
|
||||
return xml_parse_file2(fd, yb, yspec, NULL, xt, NULL);
|
||||
}
|
||||
|
||||
/*! FSM to detect substring
|
||||
*/
|
||||
static inline int
|
||||
|
|
@ -2006,32 +2166,38 @@ FSM(char *tag,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Read an XML definition from file and parse it into a parse-tree.
|
||||
/*! Read an XML definition from file and parse it into a parse-tree, advanced API
|
||||
*
|
||||
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
|
||||
* @param[in] endtag Read until encounter "endtag" in the stream, or NULL
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error with clicon_err called
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
|
||||
* @retval -1 Error with clicon_err called. Includes parse error
|
||||
*
|
||||
* @code
|
||||
* cxobj *xt = NULL;
|
||||
* cxobj *xerr = NULL;
|
||||
* int fd;
|
||||
* fd = open(filename, O_RDONLY);
|
||||
* xml_parse_file(fd, "</config>", yspec, &xt);
|
||||
* if ((ret = xml_parse_file2(fd, YB_TOP, yspec, "</config>", &xt, &xerr)) < 0)
|
||||
* err;
|
||||
* xml_free(xt);
|
||||
* @endcode
|
||||
* @see xml_parse_string
|
||||
* @see xml_parse_va
|
||||
* @see xml_parse_file
|
||||
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
|
||||
* @note May block on file I/O
|
||||
*/
|
||||
int
|
||||
xml_parse_file(int fd,
|
||||
char *endtag,
|
||||
xml_parse_file2(int fd,
|
||||
enum yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xt)
|
||||
char *endtag,
|
||||
cxobj **xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
|
|
@ -2043,6 +2209,7 @@ xml_parse_file(int fd,
|
|||
int endtaglen = 0;
|
||||
int state = 0;
|
||||
int oldxmlbuflen;
|
||||
int failed = 0;
|
||||
|
||||
if (endtag != NULL)
|
||||
endtaglen = strlen(endtag);
|
||||
|
|
@ -2069,8 +2236,10 @@ xml_parse_file(int fd,
|
|||
if (*xt == NULL)
|
||||
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
|
||||
goto done;
|
||||
if (_xml_parse(ptr, yspec, *xt) < 0)
|
||||
if ((ret = _xml_parse(ptr, yb, yspec, *xt, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
break;
|
||||
}
|
||||
if (len>=xmlbuflen-1){ /* Space: one for the null character */
|
||||
|
|
@ -2084,7 +2253,7 @@ xml_parse_file(int fd,
|
|||
ptr = xmlbuf;
|
||||
}
|
||||
} /* while */
|
||||
retval = 0;
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
if (retval < 0 && *xt){
|
||||
free(*xt);
|
||||
|
|
@ -2095,12 +2264,55 @@ xml_parse_file(int fd,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Read an XML definition from string and parse it into a parse-tree.
|
||||
/*! Read an XML definition from string and parse it into a parse-tree, advanced API
|
||||
*
|
||||
* @param[in] str String containing XML definition.
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt Pointer to XML parse tree. If empty will be created.
|
||||
* @param[out] xerr Reason for failure (yang assignment not made)
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial)
|
||||
* @retval -1 Error with clicon_err called. Includes parse error
|
||||
*
|
||||
* @code
|
||||
* cxobj *xt = NULL;
|
||||
* cxobj *xerr = NULL;
|
||||
* if (xml_parse_string2(str, YB_TOP, yspec, &xt, &xerr) < 0)
|
||||
* err;
|
||||
* if (xml_rootchild(xt, 0, &xt) < 0) # If you want to remove TOP
|
||||
* err;
|
||||
* @endcode
|
||||
* @see xml_parse_file
|
||||
* @see xml_parse_va
|
||||
* @note You need to free the xml parse tree after use, using xml_free()
|
||||
* @note If empty on entry, a new TOP xml will be created named "top"
|
||||
*/
|
||||
int
|
||||
xml_parse_string2(const char *str,
|
||||
enum yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (*xt == NULL){
|
||||
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
return _xml_parse(str, yb, yspec, *xt, xerr);
|
||||
}
|
||||
|
||||
/*! Read an XML definition from string and parse it into a parse-tree
|
||||
*
|
||||
* @param[in] str String containing XML definition.
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in,out] xt Pointer to XML parse tree. If empty will be created.
|
||||
* @retval 0 OK
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial)
|
||||
* @retval -1 Error with clicon_err called. Includes parse error
|
||||
*
|
||||
* @code
|
||||
|
|
@ -2118,12 +2330,24 @@ xml_parse_file(int fd,
|
|||
int
|
||||
xml_parse_string(const char *str,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xtop)
|
||||
cxobj **xt)
|
||||
{
|
||||
if (*xtop == NULL)
|
||||
if ((*xtop = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
|
||||
enum yang_bind yb = YB_PARENT;
|
||||
|
||||
if (xt==NULL){
|
||||
clicon_err(OE_XML, EINVAL, "xt is NULL");
|
||||
return -1;
|
||||
return _xml_parse(str, yspec, *xtop);
|
||||
}
|
||||
if (*xt == NULL){
|
||||
yb = YB_TOP; /* ad-hoc #1 */
|
||||
if ((*xt = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
else{
|
||||
if (xml_spec(*xt) == NULL)
|
||||
yb = YB_TOP; /* ad-hoc #2 */
|
||||
}
|
||||
return _xml_parse(str, yb, yspec, *xt, NULL);
|
||||
}
|
||||
|
||||
/*! Read XML from var-arg list and parse it into xml tree
|
||||
|
|
@ -2133,9 +2357,9 @@ xml_parse_string(const char *str,
|
|||
called 'top' will be created. Call xml_free() after use
|
||||
* @param[in] yspec Yang specification, or NULL
|
||||
* @param[in] format Format string for stdarg according to printf(3)
|
||||
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error with clicon_err called
|
||||
* @retval 1 Parse OK and all yang assignment made
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial)
|
||||
* @retval -1 Error with clicon_err called. Includes parse error
|
||||
*
|
||||
* @code
|
||||
* cxobj *xt = NULL;
|
||||
|
|
@ -2168,9 +2392,7 @@ xml_parse_va(cxobj **xtop,
|
|||
va_start(args, format);
|
||||
len = vsnprintf(str, len, format, args) + 1;
|
||||
va_end(args);
|
||||
if (xml_parse_string(str, yspec, xtop) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
retval = xml_parse_string(str, yspec, xtop); /* xml_parse_string2 */
|
||||
done:
|
||||
if (str)
|
||||
free(str);
|
||||
|
|
@ -2201,6 +2423,7 @@ xml_copy_one(cxobj *x0,
|
|||
if ((s = xml_prefix(x0))) /* malloced string */
|
||||
if ((xml_prefix_set(x1, s)) < 0)
|
||||
goto done;
|
||||
xml_spec_set(x1, xml_spec(x0));
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -2219,6 +2442,7 @@ xml_copy_one(cxobj *x0,
|
|||
* if (xml_copy(x0, x1) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
* @see xml_dup
|
||||
*/
|
||||
int
|
||||
xml_copy(cxobj *x0,
|
||||
|
|
@ -2249,6 +2473,7 @@ xml_copy(cxobj *x0,
|
|||
* x1 = xml_dup(x0);
|
||||
* @endcode
|
||||
* Note, returned tree should be freed as: xml_free(x1)
|
||||
* @see xml_cp
|
||||
*/
|
||||
cxobj *
|
||||
xml_dup(cxobj *x0)
|
||||
|
|
@ -2357,7 +2582,6 @@ cxvec_prepend(cxobj *x,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Apply a function call recursively on all xml node children recursively
|
||||
* Recursively traverse all xml nodes in a parse-tree and apply fn(arg) for
|
||||
* each object found. The function is called with the xml node and an
|
||||
|
|
@ -2751,4 +2975,3 @@ clicon_log_xml(int level,
|
|||
free(msg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -445,7 +446,7 @@ clixon_xml_changelog_init(clicon_handle h)
|
|||
clicon_err(OE_UNIX, errno, "open(%s)", filename);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(fd, NULL, yspec, &xt) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xt) < 0)
|
||||
goto done;
|
||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -65,6 +66,8 @@
|
|||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_xml_nsctx.h"
|
||||
#include "clixon_xpath_ctx.h"
|
||||
|
|
@ -612,12 +615,19 @@ xml_tree_prune_flagged_sub(cxobj *xt,
|
|||
int iskey;
|
||||
int anykey=0;
|
||||
yang_stmt *yt;
|
||||
int i;
|
||||
|
||||
mark = 0;
|
||||
yt = xml_spec(xt); /* xan be null */
|
||||
x = NULL;
|
||||
xprev = x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
i = 0;
|
||||
while ((x = xml_child_each(xt, x, -1)) != NULL) {
|
||||
i++;
|
||||
if (xml_type(x) != CX_ELMNT){
|
||||
xprev = x;
|
||||
continue;
|
||||
}
|
||||
if (xml_flag(x, flag) == test?flag:0){
|
||||
/* Pass test */
|
||||
mark++;
|
||||
|
|
@ -641,8 +651,9 @@ xml_tree_prune_flagged_sub(cxobj *xt,
|
|||
if (submark)
|
||||
mark++;
|
||||
else{ /* Safe with xml_child_each if last */
|
||||
if (xml_purge(x) < 0)
|
||||
if (xml_child_rm(xt, i-1) < 0)
|
||||
goto done;
|
||||
i--;
|
||||
x = xprev;
|
||||
}
|
||||
xprev = x;
|
||||
|
|
@ -651,13 +662,20 @@ xml_tree_prune_flagged_sub(cxobj *xt,
|
|||
if (anykey && !mark){
|
||||
x = NULL;
|
||||
xprev = x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
i = 0;
|
||||
while ((x = xml_child_each(xt, x, -1)) != NULL) {
|
||||
i++;
|
||||
if (xml_type(x) != CX_ELMNT){
|
||||
xprev = x;
|
||||
continue;
|
||||
}
|
||||
/* 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)
|
||||
if (xml_child_rm(xt, i-1) < 0)
|
||||
goto done;
|
||||
i--;
|
||||
x = xprev;
|
||||
}
|
||||
xprev = x;
|
||||
|
|
@ -710,7 +728,6 @@ xml_tree_prune_flagged(cxobj *xt,
|
|||
*/
|
||||
static int
|
||||
add_namespace(cxobj *x1, /* target */
|
||||
cxobj *x1p,
|
||||
char *prefix1,
|
||||
char *namespace)
|
||||
{
|
||||
|
|
@ -746,6 +763,47 @@ add_namespace(cxobj *x1, /* target */
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Change namespace of XML node
|
||||
*
|
||||
* @param[in] x XML node
|
||||
* @param[in] namespace Change to this namespace (if ns does not exist in tree)
|
||||
* @param[in] prefix If change, use this prefix
|
||||
* @param 0 OK
|
||||
* @param -1 Error
|
||||
*/
|
||||
int
|
||||
xml_namespace_change(cxobj *x,
|
||||
char *namespace,
|
||||
char *prefix)
|
||||
{
|
||||
int retval = -1;
|
||||
char *ns0 = NULL; /* existing namespace */
|
||||
char *prefix0 = NULL; /* existing prefix */
|
||||
|
||||
ns0 = NULL;
|
||||
if (xml2ns(x, xml_prefix(x), &ns0) < 0)
|
||||
goto done;
|
||||
if (ns0 && strcmp(ns0, namespace) == 0)
|
||||
goto ok; /* Already has right namespace */
|
||||
/* Is namespace already declared? */
|
||||
if (xml2prefix(x, namespace, &prefix0) == 1){
|
||||
/* Yes it is declared and the prefix is pexists */
|
||||
if (xml_prefix_set(x, prefix0) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* Namespace does not exist, add it */
|
||||
/* Clear old prefix if any */
|
||||
if (xml_prefix_set(x, NULL) < 0)
|
||||
goto done;
|
||||
if (add_namespace(x, prefix0, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Add default values (if not set)
|
||||
* @param[in] xt XML tree with some node marked
|
||||
* @param[in] arg Ignored
|
||||
|
|
@ -799,7 +857,7 @@ xml_default(cxobj *xt,
|
|||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
if (add_namespace(xc, xt, prefix, namespace) < 0)
|
||||
if (add_namespace(xc, prefix, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
|
@ -878,29 +936,34 @@ xml_non_config_data(cxobj *xt,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Find yang spec association of XML node for incoming RPC
|
||||
/*! Find yang spec association of XML node for incoming RPC starting with <rpc>
|
||||
*
|
||||
* Incoming RPC has an "input" structure that is not taken care of by xml_spec_populate
|
||||
* @param[in] xt XML tree node
|
||||
* @param[in] arg Yang spec
|
||||
* @retval 0 OK
|
||||
* @param[in] xrpc XML rpc node
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 1 OK yang assignment made
|
||||
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
|
||||
* @retval -1 Error
|
||||
* The
|
||||
* @code
|
||||
* xml_apply(xc, CX_ELMNT, xml_spec_populate_rpc_input, yspec)
|
||||
* if (xml_spec_populate_rpc(h, x, NULL) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
* @see xml_spec_populate For other generic cases
|
||||
* @see xml_spec_populate_rpc_reply
|
||||
*/
|
||||
int
|
||||
xml_spec_populate_rpc_input(clicon_handle h,
|
||||
cxobj *xrpc,
|
||||
yang_stmt *yspec)
|
||||
xml_spec_populate_rpc(cxobj *xrpc,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yrpc = NULL; /* yang node */
|
||||
yang_stmt *ymod=NULL; /* yang module */
|
||||
yang_stmt *yi = NULL; /* input */
|
||||
cxobj *x;
|
||||
int ret;
|
||||
|
||||
if ((strcmp(xml_name(xrpc), "rpc")) != 0){
|
||||
clicon_err(OE_UNIX, EINVAL, "RPC expected");
|
||||
|
|
@ -921,14 +984,225 @@ xml_spec_populate_rpc_input(clicon_handle h,
|
|||
* recursive population to work. Therefore, assign input yang
|
||||
* to rpc level although not 100% intuitive */
|
||||
xml_spec_set(x, yi);
|
||||
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if ((ret = xml_spec_populate_parent(x, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Find yang spec association of XML node for outgoing RPC starting with <rpc-reply>
|
||||
*
|
||||
* Incoming RPC has an "input" structure that is not taken care of by xml_spec_populate
|
||||
* @param[in] xrpc XML rpc node
|
||||
* @param[in] name Name of RPC (not seen in output/reply)
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 1 OK yang assignment made
|
||||
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
|
||||
* @retval -1 Error
|
||||
*
|
||||
* @code
|
||||
* if (xml_spec_populate_rpc_reply(x, "get-config", yspec, name) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
* @see xml_spec_populate For other generic cases
|
||||
*/
|
||||
int
|
||||
xml_spec_populate_rpc_reply(cxobj *xrpc,
|
||||
char *name,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yrpc = NULL; /* yang node */
|
||||
yang_stmt *ymod=NULL; /* yang module */
|
||||
yang_stmt *yo = NULL; /* output */
|
||||
cxobj *x;
|
||||
int ret;
|
||||
|
||||
if (strcmp(xml_name(xrpc), "rpc-reply")){
|
||||
clicon_err(OE_UNIX, EINVAL, "rpc-reply expected");
|
||||
goto done;
|
||||
}
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
|
||||
if (ys_module_by_xml(yspec, x, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod == NULL)
|
||||
continue;
|
||||
if ((yrpc = yang_find(ymod, Y_RPC, name)) == NULL)
|
||||
continue;
|
||||
// xml_spec_set(xrpc, yrpc);
|
||||
if ((yo = yang_find(yrpc, Y_OUTPUT, NULL)) == NULL)
|
||||
continue;
|
||||
/* xml_spec_populate need to have parent with yang spec for
|
||||
* recursive population to work. Therefore, assign input yang
|
||||
* to rpc level although not 100% intuitive */
|
||||
break;
|
||||
}
|
||||
if (yo != NULL){
|
||||
xml_spec_set(xrpc, yo);
|
||||
if ((ret = xml_spec_populate(xrpc, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Associate XML node x with x:s parents yang:s matching child
|
||||
*
|
||||
* @param[in] xt XML tree node
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 2 OK yang assignment made
|
||||
* @retval 1 OK, Yang assignment not made because yang parent is anyxml or anydata
|
||||
* @retval 0 Yang assigment not made and xerr set
|
||||
* @retval -1 Error
|
||||
* @see populate_self_top
|
||||
*/
|
||||
static int
|
||||
populate_self_parent(cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *y = NULL; /* yang node */
|
||||
yang_stmt *yparent; /* yang parent */
|
||||
cxobj *xp = NULL; /* xml parent */
|
||||
char *name;
|
||||
char *ns = NULL; /* XML namespace of xt */
|
||||
char *nsy = NULL; /* Yang namespace of xt */
|
||||
|
||||
xp = xml_parent(xt);
|
||||
name = xml_name(xt);
|
||||
if (xp == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing parent") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if ((yparent = xml_spec(xp)) == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing parent yang node") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (yang_keyword_get(yparent) == Y_ANYXML || yang_keyword_get(yparent) == Y_ANYDATA){
|
||||
retval = 1;
|
||||
goto done;
|
||||
}
|
||||
if ((y = yang_find_datanode(yparent, name)) == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing matching yang node") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
|
||||
goto done;
|
||||
nsy = yang_find_mynamespace(y);
|
||||
if (ns == NULL || nsy == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* Assign spec only if namespaces match */
|
||||
if (strcmp(ns, nsy) != 0){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
xml_spec_set(xt, y);
|
||||
retval = 2;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Associate XML node x with yang spec y by going through all top-level modules and finding match
|
||||
*
|
||||
* @param[in] xt XML tree node
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 2 OK yang assignment made
|
||||
* @retval 1 OK, Yang assignment not made because yang parent is anyxml or anydata
|
||||
* @retval 0 yang assigment not made and xerr set
|
||||
* @retval -1 Error
|
||||
* @see populate_self_parent
|
||||
*/
|
||||
static int
|
||||
populate_self_top(cxobj *xt,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *y = NULL; /* yang node */
|
||||
yang_stmt *ymod; /* yang module */
|
||||
char *name;
|
||||
char *ns = NULL; /* XML namespace of xt */
|
||||
char *nsy = NULL; /* Yang namespace of xt */
|
||||
|
||||
name = xml_name(xt);
|
||||
if (yspec == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing yang spec") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (ys_module_by_xml(yspec, xt, &ymod) < 0)
|
||||
goto done;
|
||||
/* ymod is "real" module, name may belong to included submodule */
|
||||
if (ymod == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "No such yang module") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing matching yang node") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
|
||||
goto done;
|
||||
nsy = yang_find_mynamespace(y);
|
||||
if (ns == NULL || nsy == NULL){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* Assign spec only if namespaces match */
|
||||
if (strcmp(ns, nsy) != 0){
|
||||
if (xerr &&
|
||||
netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
xml_spec_set(xt, y);
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Find yang spec association of XML node
|
||||
|
|
@ -938,94 +1212,110 @@ xml_spec_populate_rpc_input(clicon_handle h,
|
|||
* XXX: maybe it can be built into the same function, but you dont know whether it is input or output rpc, so
|
||||
* it seems like you need specialized functions.
|
||||
* @param[in] xt XML tree node
|
||||
* @param[in] arg Yang spec
|
||||
* @retval 0 OK
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 1 OK yang assignment made
|
||||
* @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
|
||||
* if (xml_spec_populate(x, yspec, NULL) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
* @note For subs to anyxml nodes will not have spec set
|
||||
* @see xml_spec_populate_rpc_input for incoming rpc
|
||||
* XXX: can give false positives
|
||||
* @see xml_spec_populate_rpc for incoming rpc
|
||||
*/
|
||||
#undef DEBUG /* Set this for debug */
|
||||
int
|
||||
xml_spec_populate(cxobj *x,
|
||||
void *arg)
|
||||
xml_spec_populate(cxobj *xt,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yspec = NULL; /* yang spec */
|
||||
yang_stmt *y = NULL; /* yang node */
|
||||
yang_stmt *yparent; /* yang parent */
|
||||
yang_stmt *ymod; /* yang module */
|
||||
cxobj *xp = NULL; /* xml parent */
|
||||
char *name;
|
||||
char *ns = NULL; /* XML namespace of x */
|
||||
char *nsy = NULL; /* Yang namespace of x */
|
||||
cxobj *xc; /* xml child */
|
||||
int ret;
|
||||
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
|
||||
|
||||
yspec = (yang_stmt*)arg;
|
||||
xp = xml_parent(x);
|
||||
name = xml_name(x);
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
|
||||
#endif
|
||||
if (xml2ns(x, xml_prefix(x), &ns) < 0)
|
||||
xc = NULL; /* Apply on children */
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_spec_populate0(xc, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL){
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s yang parent:%s", __FUNCTION__, yang_argument_get(yparent));
|
||||
#endif
|
||||
y = yang_find_datanode(yparent, name);
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
}
|
||||
else if (yspec){ /* XXX this gives false positives */
|
||||
if (ys_module_by_xml(yspec, x, &ymod) < 0)
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
xml_spec_populate_parent(cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xc; /* xml child */
|
||||
int ret;
|
||||
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
|
||||
|
||||
xc = NULL; /* Apply on children */
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
|
||||
goto done;
|
||||
/* ymod is "real" module, name may belong to included submodule */
|
||||
if (ymod != NULL){
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s %s mod:%s", __FUNCTION__, name, yang_argument_get(ymod));
|
||||
#endif
|
||||
y = yang_find_schemanode(ymod, name);
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else
|
||||
clicon_debug(1, "%s %s mod:NULL", __FUNCTION__, name);
|
||||
#endif
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
if (y) {
|
||||
nsy = yang_find_mynamespace(y);
|
||||
if (ns == NULL || nsy == NULL){
|
||||
clicon_err(OE_XML, EFAULT, "Namespace NULL");
|
||||
|
||||
int
|
||||
xml_spec_populate0(cxobj *xt,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xc; /* xml child */
|
||||
int ret;
|
||||
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
|
||||
|
||||
if ((ret = populate_self_top(xt, yspec, xerr)) < 0)
|
||||
goto done;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
|
||||
#endif
|
||||
/* Assign spec only if namespaces match */
|
||||
if (strcmp(ns, nsy) == 0){
|
||||
#if 0
|
||||
/* Cornercase:
|
||||
* The XML parser may have kept pretty-printed whitespaces in empty nodes, eg "<x> </x>"
|
||||
* since it is valid leaf(-list) content.
|
||||
* But if it is not a container, list or something, else, the content should be stripped.
|
||||
* But we cannot do this because of false positives (above)
|
||||
*/
|
||||
if (xml_spec(x) == NULL && /* Not assigned yet, ensure to do just once,... */
|
||||
yang_keyword_get(y) != Y_LEAF &&
|
||||
yang_keyword_get(y) != Y_LEAF_LIST){
|
||||
if (xml_rm_children(x, CX_BODY) < 0)
|
||||
if (ret == 1){
|
||||
xc = NULL; /* Apply on children */
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
xml_spec_set(x, y);
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
else{
|
||||
#ifdef DEBUG
|
||||
clicon_debug(1, "%s y:NULL", __FUNCTION__);
|
||||
#endif
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
retval = 0;
|
||||
|
||||
int
|
||||
xml_spec_populate0_parent(cxobj *xt,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xc; /* xml child */
|
||||
int ret;
|
||||
int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
|
||||
|
||||
if ((ret = populate_self_parent(xt, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
else if (ret != 1){ /* 1 means anyxml parent */
|
||||
xc = NULL; /* Apply on children */
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_spec_populate0_parent(xc, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
retval = (failed==0) ? 1 : 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
|
@ -1242,11 +1532,8 @@ assign_namespaces(cxobj *x0, /* source */
|
|||
* Check if it is exists in x1 itself */
|
||||
if (xml2prefix(x1, namespace, &pexist) == 1){
|
||||
/* Yes it exists, but is it equal? */
|
||||
if ((pexist == NULL && prefix0 == NULL) ||
|
||||
(pexist && prefix0 &&
|
||||
strcmp(pexist, prefix0)==0)){ /* Equal, reuse */
|
||||
;
|
||||
}
|
||||
if (clicon_strcmp(pexist, prefix0) == 0)
|
||||
; /* Equal, reuse */
|
||||
else{ /* namespace exist, but not equal, use existing */
|
||||
/* Add prefix to x1, if any */
|
||||
if (pexist && xml_prefix_set(x1, pexist) < 0)
|
||||
|
|
@ -1254,8 +1541,7 @@ assign_namespaces(cxobj *x0, /* source */
|
|||
}
|
||||
goto ok; /* skip */
|
||||
}
|
||||
else
|
||||
{ /* namespace does not exist in target x1, use source prefix
|
||||
else { /* namespace does not exist in target x1, use source prefix
|
||||
* use the prefix defined in the module
|
||||
*/
|
||||
if (isroot){
|
||||
|
|
@ -1272,7 +1558,7 @@ assign_namespaces(cxobj *x0, /* source */
|
|||
}
|
||||
}
|
||||
}
|
||||
if (add_namespace(x1, x1p, prefix1, namespace) < 0)
|
||||
if (add_namespace(x1, prefix1, namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -356,10 +358,20 @@ xml_nsctx_yang(yang_stmt *yn,
|
|||
*
|
||||
* That is, create a "canonical" XML namespace mapping from all loaded yang
|
||||
* modules which are children of the yang specification.
|
||||
* ALso add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
|
||||
* Also add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
|
||||
* Fully explore all prefix:namespace pairs of all yang modules
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[out] ncp XML namespace context
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
* cvec *nsc = NULL;
|
||||
* yang_stmt *yspec = clicon_dbspec_yang(h);
|
||||
* if (xml_nsctx_yangspec(yspec, &nsc) < 0)
|
||||
* goto done;
|
||||
* ...
|
||||
* cvec_free(nsc);
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_nsctx_yangspec(yang_stmt *yspec,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -47,20 +48,23 @@ struct xml_parse_yacc_arg{
|
|||
char *ya_parse_string; /* original (copy of) parse string */
|
||||
int ya_linenum; /* Number of \n in parsed buffer */
|
||||
void *ya_lexbuf; /* internal parse buffer from lex */
|
||||
|
||||
cxobj *ya_xelement; /* xml active element */
|
||||
cxobj *ya_xparent; /* xml parent element*/
|
||||
cxobj *ya_xtop; /* cxobj top element (fixed) */
|
||||
cxobj *ya_xelement; /* cxobj active element (changes with parse context) */
|
||||
cxobj *ya_xparent; /* cxobj parent element (changes with parse context) */
|
||||
yang_stmt *ya_yspec; /* If set, top-level yang-spec */
|
||||
int ya_lex_state; /* lex return state */
|
||||
cxobj **ya_xvec; /* Vector of created top-level nodes (to know which are created) */
|
||||
size_t ya_xlen; /* Length of ya_xvec */
|
||||
};
|
||||
typedef struct xml_parse_yacc_arg clixon_xml_yacc;
|
||||
|
||||
extern char *clixon_xml_parsetext;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya);
|
||||
int clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya);
|
||||
int clixon_xml_parsel_init(clixon_xml_yacc *ya);
|
||||
int clixon_xml_parsel_exit(clixon_xml_yacc *ya);
|
||||
|
||||
int clixon_xml_parsel_linenr(void);
|
||||
int clixon_xml_parselex(void *);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -66,7 +68,7 @@
|
|||
#define YY_NO_INPUT
|
||||
|
||||
/* typecast macro */
|
||||
#define _YA ((struct xml_parse_yacc_arg *)_ya)
|
||||
#define _YA ((clixon_xml_yacc *)_ya)
|
||||
|
||||
#undef clixon_xml_parsewrap
|
||||
int clixon_xml_parsewrap(void)
|
||||
|
|
@ -182,7 +184,7 @@ ncname {namestart}{namechar}*
|
|||
/*! Initialize XML scanner.
|
||||
*/
|
||||
int
|
||||
clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya)
|
||||
clixon_xml_parsel_init(clixon_xml_yacc *ya)
|
||||
{
|
||||
BEGIN(START);
|
||||
ya->ya_lexbuf = yy_scan_string (ya->ya_parse_string);
|
||||
|
|
@ -193,7 +195,7 @@ clixon_xml_parsel_init(struct xml_parse_yacc_arg *ya)
|
|||
|
||||
/*! Exit xml scanner */
|
||||
int
|
||||
clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya)
|
||||
clixon_xml_parsel_exit(clixon_xml_yacc *ya)
|
||||
{
|
||||
yy_delete_buffer(ya->ya_lexbuf);
|
||||
clixon_xml_parselex_destroy(); /* modern */
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -58,7 +60,7 @@
|
|||
%{
|
||||
|
||||
/* typecast macro */
|
||||
#define _YA ((struct xml_parse_yacc_arg *)_ya)
|
||||
#define _YA ((clixon_xml_yacc *)_ya)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -82,10 +84,13 @@
|
|||
#include "clixon_xml_parse.h"
|
||||
|
||||
void
|
||||
clixon_xml_parseerror(void *_ya, char *s)
|
||||
clixon_xml_parseerror(void *_ya,
|
||||
char *s)
|
||||
{
|
||||
clicon_err(OE_XML, XMLPARSE_ERRNO, "xml_parse: line %d: %s: at or before: %s",
|
||||
_YA->ya_linenum, s, clixon_xml_parsetext);
|
||||
_YA->ya_linenum,
|
||||
s,
|
||||
clixon_xml_parsetext);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +99,7 @@ clixon_xml_parseerror(void *_ya, char *s)
|
|||
* there may also be some leakage here on NULL return
|
||||
*/
|
||||
static int
|
||||
xml_parse_content(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_content(clixon_xml_yacc *ya,
|
||||
char *str)
|
||||
{
|
||||
cxobj *xn = ya->ya_xelement;
|
||||
|
|
@ -120,7 +125,7 @@ xml_parse_content(struct xml_parse_yacc_arg *ya,
|
|||
* But if there is an element, then skip all whitespace.
|
||||
*/
|
||||
static int
|
||||
xml_parse_whitespace(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_whitespace(clixon_xml_yacc *ya,
|
||||
char *str)
|
||||
{
|
||||
cxobj *xn = ya->ya_xelement;
|
||||
|
|
@ -151,7 +156,7 @@ xml_parse_whitespace(struct xml_parse_yacc_arg *ya,
|
|||
}
|
||||
|
||||
static int
|
||||
xml_parse_version(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_version(clixon_xml_yacc *ya,
|
||||
char *ver)
|
||||
{
|
||||
if(strcmp(ver, "1.0")){
|
||||
|
|
@ -164,59 +169,34 @@ xml_parse_version(struct xml_parse_yacc_arg *ya,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Parse Qualified name --> Unprefixed name
|
||||
* @param[in] ya XML parser yacc handler struct
|
||||
* @param[in] localpart Name
|
||||
* @note the call to xml_child_spec() may not have xmlns attribute read yet XXX
|
||||
*/
|
||||
static int
|
||||
xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya,
|
||||
char *name)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x;
|
||||
yang_stmt *y = NULL; /* yang node */
|
||||
cxobj *xp; /* xml parent */
|
||||
|
||||
xp = ya->ya_xparent;
|
||||
if ((x = xml_new(name, xp, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0)
|
||||
goto done;
|
||||
if (y && xml_spec_set(x, y) < 0)
|
||||
goto done;
|
||||
ya->ya_xelement = x;
|
||||
retval = 0;
|
||||
done:
|
||||
free(name);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Parse Qualified name -> PrefixedName
|
||||
/*! Parse Qualified name -> (Un)PrefixedName
|
||||
*
|
||||
* This is where all (parsed) xml elements are created
|
||||
* @param[in] ya XML parser yacc handler struct
|
||||
* @param[in] prefix Prefix, namespace, or NULL
|
||||
* @param[in] localpart Name
|
||||
*/
|
||||
static int
|
||||
xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_prefixed_name(clixon_xml_yacc *ya,
|
||||
char *prefix,
|
||||
char *name)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x;
|
||||
yang_stmt *y = NULL; /* yang node */
|
||||
cxobj *xp; /* xml parent */
|
||||
|
||||
xp = ya->ya_xparent;
|
||||
if ((x = xml_new(name, xp, NULL)) == NULL)
|
||||
goto done;
|
||||
if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0)
|
||||
goto done;
|
||||
if (y && xml_spec_set(x, y) < 0)
|
||||
goto done;
|
||||
if (xml_prefix_set(x, prefix) < 0)
|
||||
xml_type_set(x, CX_ELMNT);
|
||||
if (prefix && xml_prefix_set(x, prefix) < 0)
|
||||
goto done;
|
||||
ya->ya_xelement = x;
|
||||
/* If topmost, add to top-list created list */
|
||||
if (xp == ya->ya_xtop){
|
||||
if (cxvec_append(x, &ya->ya_xvec, &ya->ya_xlen) < 0)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (prefix)
|
||||
|
|
@ -226,7 +206,7 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya,
|
|||
}
|
||||
|
||||
static int
|
||||
xml_parse_endslash_pre(struct xml_parse_yacc_arg *ya)
|
||||
xml_parse_endslash_pre(clixon_xml_yacc *ya)
|
||||
{
|
||||
ya->ya_xparent = ya->ya_xelement;
|
||||
ya->ya_xelement = NULL;
|
||||
|
|
@ -234,7 +214,7 @@ xml_parse_endslash_pre(struct xml_parse_yacc_arg *ya)
|
|||
}
|
||||
|
||||
static int
|
||||
xml_parse_endslash_mid(struct xml_parse_yacc_arg *ya)
|
||||
xml_parse_endslash_mid(clixon_xml_yacc *ya)
|
||||
{
|
||||
if (ya->ya_xelement != NULL)
|
||||
ya->ya_xelement = xml_parent(ya->ya_xelement);
|
||||
|
|
@ -245,7 +225,7 @@ xml_parse_endslash_mid(struct xml_parse_yacc_arg *ya)
|
|||
}
|
||||
|
||||
static int
|
||||
xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
|
||||
xml_parse_endslash_post(clixon_xml_yacc *ya)
|
||||
{
|
||||
ya->ya_xelement = NULL;
|
||||
return 0;
|
||||
|
|
@ -261,7 +241,7 @@ xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
|
|||
* @param[in] name
|
||||
*/
|
||||
static int
|
||||
xml_parse_bslash(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_bslash(clixon_xml_yacc *ya,
|
||||
char *prefix,
|
||||
char *name)
|
||||
{
|
||||
|
|
@ -309,7 +289,7 @@ xml_parse_bslash(struct xml_parse_yacc_arg *ya,
|
|||
* - PrefixedAttName: xmlns:NAME
|
||||
*/
|
||||
static int
|
||||
xml_parse_attr(struct xml_parse_yacc_arg *ya,
|
||||
xml_parse_attr(clixon_xml_yacc *ya,
|
||||
char *prefix,
|
||||
char *name,
|
||||
char *attval)
|
||||
|
|
@ -389,7 +369,7 @@ element : '<' qname attrs element1
|
|||
{ clicon_debug(2, "element -> < qname attrs element1"); }
|
||||
;
|
||||
|
||||
qname : NAME { if (xml_parse_unprefixed_name(_YA, $1) < 0) YYABORT;
|
||||
qname : NAME { if (xml_parse_prefixed_name(_YA, NULL, $1) < 0) YYABORT;
|
||||
clicon_debug(2, "qname -> NAME %s", $1);}
|
||||
| NAME ':' NAME { if (xml_parse_prefixed_name(_YA, $1, $3) < 0) YYABORT;
|
||||
clicon_debug(2, "qname -> NAME : NAME");}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2020 Olof Hagsand
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -135,61 +136,6 @@ xml_cv_cache(cxobj *x,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Given a child name and an XML object, return yang stmt of child
|
||||
* If no xml parent, find root yang stmt matching name
|
||||
* @param[in] x Child
|
||||
* @param[in] xp XML parent, can be NULL.
|
||||
* @param[in] yspec Yang specification (top level)
|
||||
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
|
||||
* @note works for import prefix, but not work for generic XML parsing where
|
||||
* xmlns and xmlns:ns are used.
|
||||
*/
|
||||
int
|
||||
xml_child_spec(cxobj *x,
|
||||
cxobj *xp,
|
||||
yang_stmt *yspec,
|
||||
yang_stmt **yresult)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *y = NULL; /* result yang node */
|
||||
yang_stmt *yparent; /* parent yang */
|
||||
yang_stmt *ymod = NULL;
|
||||
yang_stmt *yi;
|
||||
char *name;
|
||||
|
||||
name = xml_name(x);
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL){
|
||||
/* First case: parent already has an associated yang statement,
|
||||
* then find matching child of that */
|
||||
if (yang_keyword_get(yparent) == Y_RPC){
|
||||
if ((yi = yang_find(yparent, Y_INPUT, NULL)) != NULL)
|
||||
y = yang_find_datanode(yi, name);
|
||||
}
|
||||
else
|
||||
y = yang_find_datanode(yparent, name);
|
||||
}
|
||||
else if (yspec){
|
||||
/* Second case, this is a "root", need to find yang stmt from spec
|
||||
*/
|
||||
if (ys_module_by_xml(yspec, xp, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod != NULL)
|
||||
y = yang_find_schemanode(ymod, name);
|
||||
}
|
||||
else
|
||||
y = NULL;
|
||||
/* kludge rpc -> input */
|
||||
if (y && yang_keyword_get(y) == Y_RPC && yang_find(y, Y_INPUT, NULL))
|
||||
y = yang_find(y, Y_INPUT, NULL);
|
||||
*yresult = y;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Help function to qsort for sorting entries in xml child vector same parent
|
||||
* @param[in] x1 object 1
|
||||
* @param[in] x2 object 2
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -123,11 +124,11 @@ static const map_str2int xpath_tree_map[] = {
|
|||
static const map_str2int axis_type_map[] = {
|
||||
{"NaN", A_NAN},
|
||||
{"ancestor", A_ANCESTOR},
|
||||
{"ancestor-or-selgf", A_ANCESTOR_OR_SELF},
|
||||
{"ancestor-or-self", A_ANCESTOR_OR_SELF},
|
||||
{"attribute", A_ATTRIBUTE},
|
||||
{"child", A_CHILD},
|
||||
{"descendant", A_DESCENDANT},
|
||||
{"descendeant-or-self", A_DESCENDANT_OR_SELF},
|
||||
{"descendant-or-self", A_DESCENDANT_OR_SELF},
|
||||
{"following", A_FOLLOWING},
|
||||
{"following-sibling", A_FOLLOWING_SIBLING},
|
||||
{"namespace", A_NAMESPACE},
|
||||
|
|
@ -702,7 +703,7 @@ xpath_first_localonly(cxobj *xcur,
|
|||
* cxobj **vec;
|
||||
* size_t veclen;
|
||||
* if (xpath_vec(xcur, nsc, "//symbol/foo", &vec, &veclen) < 0)
|
||||
* goto err;
|
||||
* err;
|
||||
* for (i=0; i<veclen; i++){
|
||||
* xn = vec[i];
|
||||
* ...
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -354,8 +355,23 @@ xp_eval_step(xp_ctx *xc0,
|
|||
}
|
||||
ctx_nodeset_replace(xc, vec, veclen);
|
||||
break;
|
||||
case A_DESCENDANT:
|
||||
case A_DESCENDANT_OR_SELF:
|
||||
for (i=0; i<xc->xc_size; i++){
|
||||
xv = xc->xc_nodeset[i];
|
||||
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, localonly, &vec, &veclen) < 0)
|
||||
goto done;
|
||||
}
|
||||
for (i=0; i<veclen; i++){
|
||||
x = vec[i];
|
||||
if (cxvec_append(x, &xc->xc_nodeset, &xc->xc_size) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (vec){
|
||||
free(vec);
|
||||
vec = NULL;
|
||||
}
|
||||
break;
|
||||
case A_DESCENDANT:
|
||||
for (i=0; i<xc->xc_size; i++){
|
||||
xv = xc->xc_nodeset[i];
|
||||
if (nodetest_recursive(xv, xs->xs_c0, CX_ELMNT, 0x0, nsc, localonly, &vec, &veclen) < 0)
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@
|
|||
#include "clixon_yang.h"
|
||||
#include "clixon_hash.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_yang_parse.h"
|
||||
#include "clixon_yang_parse_lib.h"
|
||||
#include "clixon_yang_cardinality.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_yang_type.h"
|
||||
#include "clixon_yang_internal.h" /* internal included by this file only, not API*/
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -74,11 +75,11 @@
|
|||
#include "clixon_xpath.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_xml_map.h"
|
||||
#include "clixon_yang_parse_lib.h"
|
||||
#include "clixon_yang_module.h"
|
||||
|
||||
modstate_diff_t *
|
||||
modstate_diff_new(void)
|
||||
|
|
@ -97,6 +98,8 @@ modstate_diff_free(modstate_diff_t *md)
|
|||
{
|
||||
if (md == NULL)
|
||||
return 0;
|
||||
if (md->md_set_id)
|
||||
free(md->md_set_id);
|
||||
if (md->md_del)
|
||||
xml_free(md->md_del);
|
||||
if (md->md_mod)
|
||||
|
|
|
|||
|
|
@ -88,13 +88,13 @@
|
|||
#include "clixon_yang_internal.h"
|
||||
#include "clixon_hash.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_data.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_yang_type.h"
|
||||
#include "clixon_yang_parse.h"
|
||||
#include "clixon_yang_cardinality.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_yang_parse_lib.h"
|
||||
|
||||
/* Size of json read buffer when reading from file*/
|
||||
|
|
|
|||
|
|
@ -94,9 +94,9 @@
|
|||
#include "clixon_hash.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_yang_type.h"
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ testname=
|
|||
# If set, enable debugging (of backend and restconf daemons)
|
||||
: ${DBG:=0}
|
||||
|
||||
# If set to 0, override starting of clixon_resrtcong in test (you bring your own)
|
||||
: ${RC:=1}
|
||||
|
||||
# Where to log restconf. Some systems may not have syslog,
|
||||
# eg logging to a file: RCLOG="-l f/www-data/restconf.log"
|
||||
: ${RCLOG:=}
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ expectpart "$(curl -s -i -X GET -H 'Accept: application/yang-data+xml' http://lo
|
|||
#<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
|
||||
|
||||
XML=$(cat <<EOF
|
||||
<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces" xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>'
|
||||
<interface xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces" xmlns:mymod="urn:example:augment"><name>e1</name><type>mymod:some-new-iftype</type><mymod:mandatory-leaf>true</mymod:mandatory-leaf><ospf xmlns="urn:example:augment"><reference-bandwidth>23</reference-bandwidth></ospf></interface>
|
||||
EOF
|
||||
)
|
||||
|
||||
|
|
@ -239,8 +239,9 @@ EOF
|
|||
new "restconf POST augment multi-namespace path e2 (middle path)"
|
||||
expectpart "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 ''
|
||||
|
||||
# XXX: All interfaces should have ospf, but only 1 or 2 have?
|
||||
new "restconf GET augment multi-namespace top"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}'
|
||||
#expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}'
|
||||
|
||||
new "restconf GET augment multi-namespace level 1"
|
||||
expectpart "$(curl -si -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080}\]}'
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ new "copy startup->candidate"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><candidate/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "copy startup->running not allowed"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><running/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>running</bad-element></error-info><error-severity>error</error-severity><error-message>namespace is: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><copy-config><target><running/></target><source><startup/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>running</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: running with parent: target in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
# Here running is empty
|
||||
new "Check running empty"
|
||||
|
|
|
|||
135
test/test_datastore_repair.sh
Executable file
135
test/test_datastore_repair.sh
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env bash
|
||||
# Test of the general-purpose (raw) upgrade mechanism.
|
||||
# Input is a startup db without mod-state info.
|
||||
# It has wrong namespace bindings and needs to remove some nodes
|
||||
# Output is a valid config woith correct namespaces and removed nods
|
||||
# The code for this is in the main example backend plugin.
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyangA=$dir/A.yang
|
||||
fyangB=$dir/B.yang
|
||||
|
||||
# Create configuration
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_MODSTATE>true</CLICON_XMLDB_MODSTATE>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
# Yang module A (base)
|
||||
cat <<EOF > $fyangA
|
||||
module A{
|
||||
prefix a;
|
||||
revision 2020-02-11;
|
||||
namespace "urn:example:a";
|
||||
container x {
|
||||
container y {
|
||||
}
|
||||
}
|
||||
list remove_me {
|
||||
key k;
|
||||
leaf k {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Yang module B (augments A)
|
||||
cat <<EOF > $fyangB
|
||||
module B{
|
||||
prefix b;
|
||||
revision 2020-02-11;
|
||||
namespace "urn:example:b";
|
||||
import A {
|
||||
prefix "a";
|
||||
}
|
||||
augment "/a:x/ngrt:y" {
|
||||
container z {
|
||||
leaf w {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# permission kludges
|
||||
sudo touch $dir/startup_db
|
||||
sudo chmod 666 $dir/startup_db
|
||||
|
||||
# This is how it should look after repair, using prefixes
|
||||
AFTER=$(cat <<EOF
|
||||
<x xmlns="urn:example:a"><y><b:z xmlns:b="urn:example:b"><b:w>foo</b:w></b:z></y></x>
|
||||
EOF
|
||||
)
|
||||
|
||||
testrun(){
|
||||
new "test params: -f $cfg -- -U"
|
||||
# Bring your own backend
|
||||
if [ $BE -ne 0 ]; then
|
||||
# kill old backend (if any)
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s startup -f $cfg -- -U"
|
||||
start_backend -s startup -f $cfg -- -U
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
fi
|
||||
|
||||
new "netconf get config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data>$AFTER</data></rpc-reply>]]>]]>$"
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
fi
|
||||
|
||||
} # end testrun
|
||||
|
||||
# Create startup db of "old" db with incorrect augment namespace tagging
|
||||
# without modstate
|
||||
cat <<EOF > $dir/startup_db
|
||||
<config>
|
||||
<x xmlns="urn:example:a">
|
||||
<y>
|
||||
<z>
|
||||
<w>foo</w>
|
||||
</z>
|
||||
</y>
|
||||
</x>
|
||||
<remove_me xmlns="urn:example:a"><k>This node is obsolete</k></remove_me>
|
||||
<remove_me xmlns="urn:example:a"><k>this too</k></remove_me>
|
||||
</config>
|
||||
EOF
|
||||
|
||||
new "general-purpose upgrade without modstate"
|
||||
testrun
|
||||
|
||||
rm -rf $dir
|
||||
|
|
@ -154,6 +154,7 @@ fi
|
|||
new "waiting"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u $wwwuser -f clixon_restconf
|
||||
|
||||
|
|
@ -161,7 +162,8 @@ new "start restconf daemon"
|
|||
start_restconf -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
wait_restconf
|
||||
fi
|
||||
|
||||
new "Set crypto to aes"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><crypto xmlns="urn:example:my-crypto">aes</crypto></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
|
||||
|
|
@ -281,8 +283,7 @@ expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypt
|
|||
|
||||
# 2. set identity in other module with restconf , read it with restconf and netconf
|
||||
new "restconf add POST instead of PUT (should fail)"
|
||||
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}'
|
||||
|
||||
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'
|
||||
new "restconf add other (des) identity using POST"
|
||||
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/example:crypto'
|
||||
|
||||
|
|
@ -308,8 +309,10 @@ expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example:crypto)"
|
|||
new "netconf get other identity"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><crypto xmlns="urn:example:my-crypto" xmlns:des="urn:example:des">des:des3</crypto></data></rpc-reply>]]>]]>$'
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
fi
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ expecteofx "$clixon_util_json" 0 '{"a":[0,1,2,3]}' "<a>0</a><a>1</a><a>2</a><a>3
|
|||
new "json parse list json" # should be {"a":[0,1,2,3]}
|
||||
expecteofx "$clixon_util_json -j" 0 '{"a":[0,1,2,3]}' '{"a":"0"}{"a":"1"}{"a":"2"}{"a":"3"}'
|
||||
|
||||
# Multi-line JOSN not pretty-print
|
||||
# Multi-line JSON not pretty-print
|
||||
JSON='{"json:c":{"a":42,"s":"string"}}'
|
||||
# Same with pretty-print
|
||||
JSONP='{
|
||||
|
|
|
|||
|
|
@ -110,23 +110,23 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type=
|
|||
|
||||
# Get path=/sender-state, state vs config
|
||||
new "netconf get /sender-state config+state"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="all"><filter type="xpath" select="/sender-state" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-state xmlns="urn:example:example"><ref>x</ref></sender-state></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="all"><filter type="xpath" select="/ex:sender-state" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-state xmlns="urn:example:example"><ref>x</ref></sender-state></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get /sender-state state-only"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="nonconfig"><filter type="xpath" select="/sender-state" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-state xmlns="urn:example:example"><ref>x</ref></sender-state></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="nonconfig"><filter type="xpath" select="/ex:sender-state" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-state xmlns="urn:example:example"><ref>x</ref></sender-state></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get /sender-state config-only"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type="xpath" select="/sender-state" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data/></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type="xpath" select="/ex:sender-state" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data/></rpc-reply>]]>]]>$'
|
||||
|
||||
# Get path=/sender-config, state vs config
|
||||
new "netconf get /sender-config config+state"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="all"><filter type="xpath" select="/sender-config" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-config xmlns="urn:example:example"><name>x</name></sender-config></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="all"><filter type="xpath" select="/ex:sender-config" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-config xmlns="urn:example:example"><name>x</name></sender-config></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get /sender-config state-only"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="nonconfig"><filter type="xpath" select="/sender-config" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data/></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="nonconfig"><filter type="xpath" select="/ex:sender-config" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data/></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf get /sender-config config-only"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type="xpath" select="/sender-config" xmlns="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-config xmlns="urn:example:example"><name>x</name></sender-config></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type="xpath" select="/ex:sender-config" xmlns:ex="urn:example:example"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-config xmlns="urn:example:example"><name>x</name></sender-config></data></rpc-reply>]]>]]>$'
|
||||
|
||||
# Negative tests,
|
||||
# Double xmlns attribute
|
||||
|
|
@ -173,7 +173,6 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="nonconfig"><filter ty
|
|||
new "netconf get / config-only ok"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get content="config"><filter type="xpath" select="/"/></get></rpc>]]>]]>' '^<rpc-reply><data><sender-config xmlns="urn:example:example"><name>y</name></sender-config></data></rpc-reply>]]>]]>$'
|
||||
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ module whitespace{
|
|||
}
|
||||
EOF
|
||||
|
||||
|
||||
new "test params: -f $cfg"
|
||||
new "test params: -f $cfg -s startup"
|
||||
|
||||
echo '<config><ex:x xmlns:ex="urn:example:whitespace">
|
||||
<ex:y> <ex:a>foo</ex:a>\n <ex:b> </ex:b></ex:y> </ex:x></config></edit-config></rpc>]]>]]>$start</config>' > $dir/startup_db
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ start_restconf -f $cfg
|
|||
new "waiting"
|
||||
wait_restconf
|
||||
|
||||
new "generate 'large' config with $perfnr list entries"
|
||||
new "generate config with $perfnr list entries"
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\">" > $fconfig
|
||||
for (( i=0; i<$perfnr; i++ )); do
|
||||
echo -n "<y><a>$i</a><b>$i</b></y>" >> $fconfig
|
||||
|
|
@ -118,6 +118,7 @@ expecteof "time -p $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<r
|
|||
# comparing to existing)
|
||||
new "netconf commit large config again"
|
||||
expecteof "time -p $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" 2>&1 | awk '/real/ {print $2}'
|
||||
|
||||
# Having a large db, get and put single entries many times
|
||||
# Note same entries in the range alreay there, db has same size
|
||||
|
||||
|
|
@ -160,6 +161,7 @@ new "restconf add $perfreq small config"
|
|||
done } 2>&1 | awk '/real/ {print $2}'
|
||||
|
||||
# CLI get (XXX why does this take so much time?)
|
||||
# See: EXPAND_ONLY_INTERACTIVE in cligen. If set it is acceptable but there are some side-effects
|
||||
new "cli get $perfreq small config 1 key index"
|
||||
{ time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
|
|
@ -205,6 +207,9 @@ done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
|
|||
#new "netconf discard-changes"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
# XXX This takes time
|
||||
# 18.69 without startup feature
|
||||
# 21.98 with startup
|
||||
new "restconf delete $perfreq small config"
|
||||
{ time -p for (( i=0; i<$perfreq; i++ )); do
|
||||
rnd=$(( ( RANDOM % $perfnr ) ))
|
||||
|
|
@ -213,7 +218,7 @@ done > /dev/null; } 2>&1 | awk '/real/ {print $2}'
|
|||
|
||||
# Now do leaf-lists istead of leafs
|
||||
|
||||
#new "generate large leaf-list config"
|
||||
#new "generate leaf-list config"
|
||||
echo -n "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><x xmlns=\"urn:example:clixon\">" > $fconfig2
|
||||
for (( i=0; i<$perfnr; i++ )); do
|
||||
echo -n "<c>$i</c>" >> $fconfig2
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ fi
|
|||
new "waiting"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u $wwwuser -f clixon_restconf
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ start_restconf -f $cfg
|
|||
|
||||
new "waiting"
|
||||
wait_restconf
|
||||
fi
|
||||
|
||||
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
||||
expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
|
||||
|
|
@ -118,10 +120,10 @@ new "restconf empty rpc, default media type should fail (JSON)"
|
|||
expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
|
||||
|
||||
new "restconf empty rpc with extra args (should fail)"
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"namespace is: urn:example:clixon"}}}
'
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: empty in namespace: urn:example:clixon"}}}
'
|
||||
|
||||
new "restconf debug rpc"
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
||||
#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf get empty config + state json"
|
||||
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["42","41","43"]}}
|
||||
|
|
@ -173,24 +175,21 @@ new "restconf Add subtree eth/0/0 to datastore using POST"
|
|||
expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces'
|
||||
|
||||
new "restconf Re-add subtree eth/0/0 which should give error"
|
||||
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
|
||||
# XXX Cant get this to work
|
||||
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"clixon-example:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'
|
||||
|
||||
new "restconf Check interfaces eth/0/0 added"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}}
|
||||
'
|
||||
|
||||
new "restconf delete interfaces"
|
||||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 ""
|
||||
expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf Check empty config"
|
||||
expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state
|
||||
"
|
||||
|
||||
new "restconf Add interfaces subtree eth/0/0 using POST"
|
||||
expectpart "$(curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 ""
|
||||
expectpart "$(curl -si -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf Check eth/0/0 added config"
|
||||
expectpart "$(curl -s -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||
|
|
@ -208,7 +207,7 @@ new "restconf Re-post eth/0/0 which should generate error"
|
|||
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||
|
||||
new "Add leaf description using POST"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "Add nothing using POST (expect fail)"
|
||||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
||||
|
|
@ -218,7 +217,7 @@ expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces
|
|||
'
|
||||
|
||||
new "restconf delete eth/0/0"
|
||||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "Check deleted eth/0/0"
|
||||
expectfn 'curl -s -G http://localhost/restconf/data' 0 "$state"
|
||||
|
|
@ -227,7 +226,7 @@ new "restconf Re-Delete eth/0/0 using none should generate error"
|
|||
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}}
'
|
||||
|
||||
new "restconf Add subtree eth/0/0 using PUT"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf get subtree"
|
||||
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}}
|
||||
|
|
@ -238,7 +237,7 @@ expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"
|
|||
'
|
||||
|
||||
new "restconf rpc using POST json wrong"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"namespace is: urn:example:clixon"}}}
'
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}
'
|
||||
|
||||
new "restconf rpc non-existing rpc without namespace"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||
|
|
@ -277,8 +276,10 @@ expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '
|
|||
new "restconf Add subtree with too many keys (expected error)"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}}
'
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
fi
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ fi
|
|||
new "waiting"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u $wwwuser -f clixon_restconf
|
||||
|
||||
|
|
@ -90,15 +91,16 @@ start_restconf -f $cfg
|
|||
|
||||
new "waiting"
|
||||
wait_restconf
|
||||
fi
|
||||
|
||||
new "restconf POST tree without key"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
||||
|
||||
new "restconf POST initial tree"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 ""
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf POST top without namespace"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"cont1"},"error-severity":"error","error-message":"Unassigned yang spec"}}}
'
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}}
'
|
||||
|
||||
new "restconf GET datastore initial"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||
|
|
@ -124,10 +126,10 @@ new "restconf POST interface without mandatory key"
|
|||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}}
'
|
||||
|
||||
new "restconf POST interface"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 ""
|
||||
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf POST interface without namespace"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}
'
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}}
'
|
||||
|
||||
new "restconf POST again"
|
||||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||
|
|
@ -136,7 +138,7 @@ new "restconf POST from top"
|
|||
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}
'
|
||||
|
||||
new "restconf DELETE"
|
||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 ""
|
||||
expectfn 'curl -si -X DELETE http://localhost/restconf/data/example:cont1' 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf GET null datastore"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
|
@ -154,34 +156,34 @@ new "restconf GET null datastore"
|
|||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"rpc-error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}}'
|
||||
|
||||
new "restconf PUT initial datastore"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf GET datastore"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
|
||||
|
||||
new "restconf PUT replace datastore"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont2":{"name":"foo"}}}' http://localhost/restconf/data)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf GET replaced datastore"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}'
|
||||
|
||||
new "restconf PUT initial datastore again"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf PUT change interface"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' http://localhost/restconf/data/example:cont1/interface=local0)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' http://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf GET datastore atm"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
|
||||
|
||||
new "restconf PUT add interface"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
new "restconf PUT change key error"
|
||||
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||
|
||||
new "restconf PUT change type to eth0 (non-key sub-element to list)"
|
||||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' http://localhost/restconf/data/example:cont1/interface=local0/type)" 0 ""
|
||||
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' http://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "restconf GET datastore eth"
|
||||
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
|
||||
|
|
@ -193,8 +195,10 @@ expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"
|
|||
new "restconf POST type x3"
|
||||
expectfn 'curl -s -X GET http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}'
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
fi
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ fi
|
|||
new "waiting"
|
||||
wait_backend
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
sudo pkill -u $wwwuser -f clixon_restconf
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ start_restconf -f $cfg
|
|||
|
||||
new "waiting"
|
||||
wait_restconf
|
||||
fi
|
||||
|
||||
new "B.1.1. Retrieve the Top-Level API Resource root"
|
||||
expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
|
||||
|
|
@ -114,7 +116,6 @@ expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http
|
|||
new "B.2.1. Add Data Resources again (conflict - not in RFC)"
|
||||
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict"
|
||||
|
||||
|
||||
new "4.5. PUT replace content (xml encoding)"
|
||||
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
|
|
@ -254,8 +255,10 @@ new 'B.3.9. "with-defaults" Parameter'
|
|||
|
||||
fi # NYI
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
new "Kill restconf daemon"
|
||||
stop_restconf
|
||||
fi
|
||||
|
||||
if [ $BE -eq 0 ]; then
|
||||
exit # BE
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ new "restconf PUT change whole list entry (same keys)"
|
|||
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 ''
|
||||
|
||||
new "restconf PUT change whole list entry (no namespace)(expect fail)"
|
||||
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Data is not prefixed with matching namespace"}}}
'
|
||||
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}}
'
|
||||
|
||||
new "restconf PUT change list entry (wrong keys)(expect fail)"
|
||||
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}
'
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -H '
|
|||
'
|
||||
|
||||
new "restconf example rpc xml in w json encoding (expect fail)"
|
||||
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Reques' "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message> on line 1: syntax error at or before: '<'</error-message></error></errors>
"
|
||||
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '<input xmlns="urn:example:clixon"><x>0</x></input>' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message>json_parse: line 1: syntax error at or before: '<'</error-message></error></errors>
"
|
||||
|
||||
new "restconf example rpc json in xml encoding (expect fail)"
|
||||
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Reques' '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>rpc</error-type><error-tag>malformed-message</error-tag><error-severity>error</error-severity><error-message>xml_parse: line 0: syntax error: at or before: "</error-message></error></errors>
'
|
||||
|
|
@ -114,7 +114,7 @@ if [ -z "$match" ]; then
|
|||
fi
|
||||
|
||||
new "restconf empty rpc with input x"
|
||||
expectpart "$(curl -iss -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"namespace is: urn:example:clixon"}}}
'
|
||||
expectpart "$(curl -iss -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: x with parent: empty in namespace: urn:example:clixon"}}}
'
|
||||
|
||||
# cornercase: optional has yang input/output sections but test without body
|
||||
new "restconf optional rpc with null input and output"
|
||||
|
|
@ -129,7 +129,7 @@ new "restconf omit mandatory"
|
|||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}}
'
|
||||
|
||||
new "restconf add extra"
|
||||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"namespace is: urn:example:clixon"}}}
'
|
||||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: example in namespace: urn:example:clixon"}}}
'
|
||||
|
||||
new "restconf wrong method"
|
||||
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}}
'
|
||||
|
|
@ -144,7 +144,7 @@ new "netconf edit-config ok"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit-config extra arg should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity><error-message>namespace is: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity><error-message>Failed to find YANG spec of XML node: extra with parent: edit-config in namespace: urn:ietf:params:xml:ns:netconf:base:1.0</error-message></rpc-error></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf edit-config empty target should fail"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target/><config/></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>data-missing</error-tag><error-app-tag>missing-choice</error-app-tag><error-info><missing-choice>config-target</missing-choice></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$'
|
||||
|
|
|
|||
85
test/test_search_index.sh
Executable file
85
test/test_search_index.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env bash
|
||||
# Test explicit search index.
|
||||
# Test done by clixon explicit-index extension
|
||||
# Test explicit indexes in lists these cases:
|
||||
# - not a key string
|
||||
# - not a key int
|
||||
# - key in an ordered-by user
|
||||
# - key in state data
|
||||
# Use instance-id for tests,since api-path can only handle keys, and xpath is too complex.
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
: ${clixon_util_path:=clixon_util_path -D $DBG -Y /usr/local/share/clixon}
|
||||
|
||||
# Number of list/leaf-list entries
|
||||
: ${nr:=10}
|
||||
|
||||
# Number of tests to generate XML for +1
|
||||
max=2
|
||||
|
||||
# XML file (alt provide it in stdin after xpath)
|
||||
for (( i=1; i<$max; i++ )); do
|
||||
eval xml$i=$dir/xml$i.xml
|
||||
done
|
||||
ydir=$dir/yang
|
||||
|
||||
if [ ! -d $ydir ]; then
|
||||
mkdir $ydir
|
||||
fi
|
||||
|
||||
cat <<EOF > $ydir/moda.yang
|
||||
module moda{
|
||||
namespace "urn:example:a";
|
||||
prefix a;
|
||||
import clixon-config {
|
||||
prefix "cc";
|
||||
}
|
||||
container x1{
|
||||
description "extra index in list with single key";
|
||||
list y{
|
||||
ordered-by system;
|
||||
key k1;
|
||||
leaf k1{
|
||||
type string;
|
||||
}
|
||||
leaf z{
|
||||
type string;
|
||||
}
|
||||
leaf i{
|
||||
description "extra index";
|
||||
type string;
|
||||
cc:search_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# key random
|
||||
rnd=$(( ( RANDOM % $nr ) ))
|
||||
# Let key index rndi be reverse of rnd
|
||||
rndi=$(( $nr - $rnd - 1 ))
|
||||
|
||||
# Single string key
|
||||
# Assign index i in reverse order
|
||||
new "generate list with $nr single string key to $xml1"
|
||||
echo -n '<x1 xmlns="urn:example:a">' > $xml1
|
||||
for (( i=0; i<$nr; i++ )); do
|
||||
let ii=$nr-$i-1
|
||||
echo -n "<y><k1>a$i</k1><z>foo$i</z><i>i$ii</i></y>" >> $xml1
|
||||
done
|
||||
echo -n '</x1>' >> $xml1
|
||||
|
||||
# How should I know it is an optimized search?
|
||||
new "instance-id single string key i=i$rndi"
|
||||
echo "$clixon_util_path -f $xml1 -y $ydir -p /a:x1/a:y[a:i=\"i$rndi\"]"
|
||||
expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /a:x1/a:y[a:i=\"i$rndi\"])" 0 "^0: <y><k1>a$rnd</k1><z>foo$rnd</z><i>i$rndi</i></y>$"
|
||||
|
||||
#rm -rf $dir
|
||||
|
||||
unset nr
|
||||
unset clixon_util_path # for other script reusing it
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# Starting clixon with outdated (or not) modules
|
||||
# This relieas on storing RFC7895 YANG Module Library modules-state info
|
||||
# This relies on storing RFC7895 YANG Module Library modules-state info
|
||||
# in the datastore (or XML files?)
|
||||
# The test is made with three Yang models A, B and C as follows:
|
||||
# Yang module A has revisions "0814-01-28" and "2019-01-01"
|
||||
|
|
|
|||
127
test/test_upgrade_simple.sh
Executable file
127
test/test_upgrade_simple.sh
Executable file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env bash
|
||||
# A very simple case - Error in this detected by msmith@netgate
|
||||
# Enable modstate and save running on a simple system without upgrade callback
|
||||
# Upgrade yang revision, but no other (upgrade) changes
|
||||
# Then start from running with modstate enabled and the new revision
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
APPNAME=simple
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
|
||||
# Create configuration
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>/usr/local/etc/clixon.xml</CLICON_CONFIGFILE>
|
||||
<CLICON_FEATURE>*:*</CLICON_FEATURE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>$APPNAME</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_CLI_MODE>hello</CLICON_CLI_MODE>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/hello/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_SOCK>/usr/local/var/hello.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/hello.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_MODSTATE>true</CLICON_XMLDB_MODSTATE>
|
||||
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
||||
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $dir/$APPNAME.yang
|
||||
module $APPNAME {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:simple";
|
||||
prefix he;
|
||||
revision 2019-04-17 {
|
||||
description
|
||||
"Clixon hello world example";
|
||||
}
|
||||
container hello{
|
||||
container world{
|
||||
presence true;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg"
|
||||
# Bring your own backend
|
||||
if [ $BE -ne 0 ]; then
|
||||
# kill old backend (if any)
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg"
|
||||
start_backend -s init -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
fi
|
||||
|
||||
new "add hello world (with modstate)"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><hello xmlns="urn:example:simple"><world/></hello></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf commit"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
|
||||
# Now add a new yang for hello
|
||||
cat <<EOF > $dir/$APPNAME.yang
|
||||
module $APPNAME {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:simple";
|
||||
prefix he;
|
||||
revision 2020-01-01 {
|
||||
description
|
||||
"Test new revision";
|
||||
}
|
||||
revision 2019-04-17 {
|
||||
description
|
||||
"Clixon hello world example";
|
||||
}
|
||||
container hello{
|
||||
container world{
|
||||
presence true;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Now start again from running with modstate enabled and new revision
|
||||
|
||||
if [ $BE -ne 0 ]; then
|
||||
|
||||
new "start backend -s running -f $cfg"
|
||||
start_backend -s running -f $cfg
|
||||
|
||||
new "waiting"
|
||||
wait_backend
|
||||
fi
|
||||
|
||||
new "netconf get config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><hello xmlns="urn:example:simple"><world/></hello></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
|
||||
rm -rf $dir
|
||||
|
||||
|
|
@ -171,7 +171,6 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></s
|
|||
new "netconf discard-changes"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
|
||||
#new "cli not defined extension"
|
||||
#new "netconf not defined extension"
|
||||
#expecteof "$clixon_netconf -qf $cfg -l o" 0 "$YANG" "Extension ex:not-defined not found"
|
||||
|
|
@ -179,7 +178,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<
|
|||
#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" 0 "Yang error: Extension ex:not-defined not found"
|
||||
|
||||
new "netconf schema resource, RFC 7895"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get><filter type="xpath" select="modules-state/module" xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"/></get></rpc>]]>]]>' '<module><name>ietf-yang-types</name><revision>2013-07-15</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace><conformance-type>implement</conformance-type></module>'
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get><filter type="xpath" select="yanglib:modules-state/yanglib:module" xmlns:yanglib="urn:ietf:params:xml:ns:yang:ietf-yang-library"/></get></rpc>]]>]]>' '<module><name>ietf-yang-types</name><revision>2013-07-15</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace><conformance-type>implement</conformance-type></module>'
|
||||
|
||||
new "netconf edit config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><y><a>1</a><b>2</b><c>5</c><val>one</val></y><d/></x></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
@ -272,7 +271,7 @@ new "netconfig config submodule"
|
|||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><mylist xmlns="urn:example:clixon"><x>a</x><subm-container><subm-leaf>foo</subm-leaf></subm-container></mylist></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
||||
new "netconf submodule get config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><mylist xmlns="urn:example:clixon"><x>a</x><subm-container><subm-leaf>foo</subm-leaf></subm-container></mylist></data></rpc-reply>]]>]]>$'
|
||||
expecteof "$clixon_netconf -qf $cfg -D 1 -l s" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><mylist xmlns="urn:example:clixon"><x>a</x><subm-container><subm-leaf>foo</subm-leaf></subm-container></mylist></data></rpc-reply>]]>]]>$'
|
||||
|
||||
new "netconf submodule validate"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
|
|||
159
test/test_yang_bind.sh
Executable file
159
test/test_yang_bind.sh
Executable file
|
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env bash
|
||||
# Check how XML/JSON associates to YANG spec in different situations
|
||||
# In more detail, xml/json usually goes through these steps:
|
||||
# 1. Parse syntax, eg map JSON/XML concrete syntax to cxobj trees
|
||||
# 2. Populate/match cxobj tree X with yang statements, ie bind each cxobj node to yang_stmt nodes
|
||||
# a. X is a top-level node (XML and JSON)
|
||||
# b. X is a not a top-level node (XML and JSON)
|
||||
# 3. Sort children
|
||||
# 4. Validation (optional)
|
||||
# These tests are for cases 2a and 2b primarily. They occur somewhat differently in XML and JSON.
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
||||
: ${clixon_util_xml:="clixon_util_xml"}
|
||||
: ${clixon_util_json:="clixon_util_json"}
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_match.xml
|
||||
fyang=$dir/match.yang
|
||||
fxml=$dir/x.xml
|
||||
fjson=$dir/x.json
|
||||
ftop=$dir/top.xml
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_NETCONF_DIR>/usr/local/lib/$APPNAME/netconf</CLICON_NETCONF_DIR>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module match{
|
||||
yang-version 1.1;
|
||||
prefix m;
|
||||
|
||||
namespace "urn:example:match";
|
||||
container a {
|
||||
description "Top level";
|
||||
presence true;
|
||||
list a {
|
||||
description "Note same as parent to catch false positives 2a/2b";
|
||||
key k;
|
||||
leaf k{
|
||||
type uint32;
|
||||
}
|
||||
}
|
||||
anyxml any;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
new "test params: -f $cfg"
|
||||
|
||||
cat <<EOF > $ftop
|
||||
<a xmlns="urn:example:match"><a><k>0</k></a></a>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fxml
|
||||
<a xmlns="urn:example:match"><a><k>43</k></a></a>
|
||||
EOF
|
||||
|
||||
new "2a XML Add a/a/k on top"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml)" 0 '^$'
|
||||
|
||||
# Subtree without namespace (maybe shouldnt work?)
|
||||
cat <<EOF > $fxml
|
||||
<a><k>42</k></a>
|
||||
EOF
|
||||
new "2b XML Add a/k under a without namespace"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml -t $ftop -T m:a)" 0 '^$'
|
||||
|
||||
# Subtree with namespace
|
||||
cat <<EOF > $fxml
|
||||
<a xmlns="urn:example:match"><k>42</k></a>
|
||||
EOF
|
||||
|
||||
new "2b XML Add a/k under a"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml -t $ftop -T m:a)" 0 '^$'
|
||||
|
||||
new "XML Add a/k on top, should fail"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml 2> /dev/null)" 255 '^$'
|
||||
|
||||
cat <<EOF > $fxml
|
||||
<a xmlns="urn:example:match"><a><k>43</k></a></a>
|
||||
EOF
|
||||
new "2b XML Add a/a/k under a should fail"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml -t $ftop -T m:a 2> /dev/null)" 255 '^$'
|
||||
|
||||
# Anyxml
|
||||
cat <<EOF > $fxml
|
||||
<any xmlns="urn:example:match"><kalle>hej</kalle></any>
|
||||
EOF
|
||||
new "XML Add any under a"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml -t $ftop -T m:a)" 0 '^$'
|
||||
|
||||
cat <<EOF > $fxml
|
||||
<a xmlns="urn:example:match"><any><kalle>hej</kalle></any></a>
|
||||
EOF
|
||||
|
||||
new "XML Add any on top"
|
||||
expectpart "$($clixon_util_xml -vy $fyang -f $fxml)" 0 '^$'
|
||||
|
||||
# OK, same thing with JSON!
|
||||
|
||||
cat <<EOF > $fjson
|
||||
{"match:a":{"a":{"k":43}}}
|
||||
EOF
|
||||
|
||||
new "2a JSON Add a/a/k on top"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson)" 0 '^$'
|
||||
|
||||
|
||||
# Subtree with namespace
|
||||
cat <<EOF > $fjson
|
||||
{"match:a":{"k":43}}
|
||||
EOF
|
||||
|
||||
new "2b JSON Add a/k under a"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson -t $ftop -T m:a)" 0 '^$'
|
||||
|
||||
new "JSON Add a/k on top, should fail"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson 2> /dev/null)" 255 '^$'
|
||||
|
||||
cat <<EOF > $fjson
|
||||
{"match:a":{"a":{"k":43}}}
|
||||
EOF
|
||||
new "2b JSON Add a/a/k under a should fail"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson -t $ftop -T m:a 2> /dev/null)" 255 '^$'
|
||||
|
||||
# Anyxml
|
||||
cat <<EOF > $fjson
|
||||
{"match:any":{"kalle":"hej"}}
|
||||
EOF
|
||||
new "JSON Add any under a"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson -t $ftop -T m:a)" 0 '^$'
|
||||
|
||||
cat <<EOF > $fjson
|
||||
{"match:a":{"any":{"kalle":"hej"}}}
|
||||
EOF
|
||||
|
||||
new "JSON Add any on top"
|
||||
expectpart "$($clixon_util_xml -Jvy $fyang -f $fjson)" 0 '^$'
|
||||
|
||||
|
||||
#rm -rf $dir
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -252,7 +254,7 @@ main(int argc, char **argv)
|
|||
clicon_err(OE_UNIX, errno, "open(%s)", xmlfilename);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(fd, "</config>", yspec, &xt) < 0)
|
||||
if (xml_parse_file(fd, yspec, &xt) < 0)
|
||||
goto done;
|
||||
close(fd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -65,7 +67,7 @@
|
|||
/* clixon */
|
||||
#include "clixon/clixon.h"
|
||||
|
||||
#define UTIL_SSL_OPTS "hD:H:"
|
||||
#define UTIL_GRPC_OPTS "hD:H:"
|
||||
|
||||
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
|
|
@ -485,7 +487,7 @@ main(int argc,
|
|||
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
goto done;
|
||||
while ((c = getopt(argc, argv, UTIL_SSL_OPTS)) != -1)
|
||||
while ((c = getopt(argc, argv, UTIL_GRPC_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ main(int argc, char **argv)
|
|||
clicon_err(OE_XML, 0, "Parsing base xml: %s", x0str);
|
||||
goto done;
|
||||
}
|
||||
if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(x0, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath);
|
||||
|
|
@ -165,7 +167,7 @@ main(int argc, char **argv)
|
|||
clicon_err(OE_XML, 0, "Parsing insert xml: %s", xistr);
|
||||
goto done;
|
||||
}
|
||||
if (xml_apply(xi, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(xi, yspec, NULL) < 0)
|
||||
goto done;
|
||||
if ((xi = xpath_first(xi, NULL, "%s", xpath)) == NULL){
|
||||
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -104,6 +105,7 @@ main(int argc,
|
|||
clicon_handle h;
|
||||
struct stat st;
|
||||
cxobj *xcfg = NULL;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init("api-path", LOG_DEBUG, CLICON_LOG_STDERR);
|
||||
|
|
@ -204,27 +206,22 @@ main(int argc,
|
|||
* If fd=0, then continue reading from stdin (after CR)
|
||||
* If fd>0, reading from file opened as argv[1]
|
||||
*/
|
||||
if (xml_parse_file(fd, "</clicon>", NULL, &x) < 0){
|
||||
if (xml_parse_file(fd, NULL, &x) < 0){
|
||||
fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate XML as well */
|
||||
if (yang_file_dir){
|
||||
cxobj *x1;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
|
||||
x1 = NULL;
|
||||
while ((x1 = xml_child_each(x, x1, CX_ELMNT)) != NULL) {
|
||||
/* Populate */
|
||||
if (xml_apply0(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(x, yspec, NULL) < 0)
|
||||
goto done;
|
||||
/* Add default values */
|
||||
if (xml_apply(x1, CX_ELMNT, xml_default, h) < 0)
|
||||
if (xml_apply(x, CX_ELMNT, xml_default, h) < 0)
|
||||
goto done;
|
||||
if ((ret = xml_yang_validate_all_top(h, x1, &xerr)) < 0)
|
||||
if ((ret = xml_yang_validate_all_top(h, x, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, x1, &xerr)) < 0)
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, x, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if ((cb = cbuf_new()) ==NULL){
|
||||
|
|
@ -236,7 +233,6 @@ main(int argc,
|
|||
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cb));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (xml_apply0(x, CX_ELMNT, xml_sort, h) < 0)
|
||||
goto done;
|
||||
if (xml_apply0(x, -1, xml_sort_verify, h) < 0)
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ main(int argc,
|
|||
}
|
||||
}
|
||||
else{
|
||||
if (xml_parse_file(fd, NULL, NULL, &xt) < 0){
|
||||
if (xml_parse_file(fd, NULL, &xt) < 0){
|
||||
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
||||
Copyright (C) 2017-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -63,6 +65,48 @@
|
|||
/* clixon */
|
||||
#include "clixon/clixon.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define UTIL_XML_OPTS "hD:f:Jjl:pvoy:Y:t:T:"
|
||||
|
||||
static int
|
||||
validate_tree(clicon_handle h,
|
||||
cxobj *xt,
|
||||
yang_stmt *yspec)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
cbuf *cbret = NULL;
|
||||
|
||||
/* should already be populated */
|
||||
/* Add default values */
|
||||
if (xml_apply(xt, CX_ELMNT, xml_default, h) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xt, -1, xml_sort_verify, h) < 0)
|
||||
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
|
||||
if ((ret = xml_yang_validate_all_top(h, xt, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, xt, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if ((cbret = cbuf_new()) ==NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (netconf_err2cb(xerr, cbret) < 0)
|
||||
goto done;
|
||||
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cbret));
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cbret)
|
||||
cbuf_free(cbret);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
|
|
@ -79,6 +123,8 @@ usage(char *argv0)
|
|||
"\t-p \t\tPretty-print output\n"
|
||||
"\t-y <filename> \tYang filename or dir (load all files)\n"
|
||||
"\t-Y <dir> \tYang dirs (can be several)\n"
|
||||
"\t-t <file>\tXML top input file (where base tree is pasted to)\n"
|
||||
"\t-T <path>\tXPath to where in top input file base should be pasted\n"
|
||||
,
|
||||
argv0);
|
||||
exit(0);
|
||||
|
|
@ -89,7 +135,8 @@ main(int argc,
|
|||
char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xt = NULL;
|
||||
int ret;
|
||||
cxobj *xt = NULL; /* Base cxobj tree parsed from xml or json */
|
||||
cxobj *xc;
|
||||
cbuf *cb = cbuf_new();
|
||||
int c;
|
||||
|
|
@ -97,18 +144,23 @@ main(int argc,
|
|||
int jsonin = 0;
|
||||
int jsonout = 0;
|
||||
char *input_filename = NULL;
|
||||
char *top_input_filename = NULL;
|
||||
char *yang_file_dir = NULL;
|
||||
yang_stmt *yspec = NULL;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
int ret;
|
||||
int pretty = 0;
|
||||
int validate = 0;
|
||||
int output = 0;
|
||||
clicon_handle h;
|
||||
struct stat st;
|
||||
int fd = 0; /* stdin */
|
||||
int fd = 0; /* base file, stdin */
|
||||
int tfd = -1; /* top file */
|
||||
cxobj *xcfg = NULL;
|
||||
cbuf *cbret = NULL;
|
||||
cxobj *xtop = NULL; /* Top tree if any */
|
||||
char *top_path = NULL;
|
||||
cxobj *xbot; /* Place in xtop where base cxobj is parsed */
|
||||
cvec *nsc = NULL;
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
|
||||
|
|
@ -124,7 +176,7 @@ main(int argc,
|
|||
clicon_conf_xml_set(h, xcfg);
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
while ((c = getopt(argc, argv, "hD:f:Jjl:pvoy:Y:")) != -1)
|
||||
while ((c = getopt(argc, argv, UTIL_XML_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
|
|
@ -162,6 +214,12 @@ main(int argc,
|
|||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 't':
|
||||
top_input_filename = optarg;
|
||||
break;
|
||||
case 'T': /* top file xpath */
|
||||
top_path = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
|
|
@ -170,6 +228,10 @@ main(int argc,
|
|||
fprintf(stderr, "-v requires -y\n");
|
||||
usage(argv[0]);
|
||||
}
|
||||
if (top_input_filename && top_path == NULL){
|
||||
fprintf(stderr, "-t requires -T\n");
|
||||
usage(argv[0]);
|
||||
}
|
||||
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst);
|
||||
/* 1. Parse yang */
|
||||
if (yang_file_dir){
|
||||
|
|
@ -188,6 +250,31 @@ main(int argc,
|
|||
goto done;
|
||||
}
|
||||
}
|
||||
/* If top file is declared, the base XML/JSON is pasted as child to the top-file.
|
||||
* This is to emulate sub-tress, not just top-level parsing.
|
||||
* Always validated
|
||||
*/
|
||||
if (top_input_filename){
|
||||
if ((tfd = open(top_input_filename, O_RDONLY)) < 0){
|
||||
clicon_err(OE_YANG, errno, "open(%s)", top_input_filename);
|
||||
goto done;
|
||||
}
|
||||
if (xml_parse_file(tfd, yspec, &xtop) < 0){
|
||||
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
|
||||
goto done;
|
||||
}
|
||||
if (validate_tree(h, xtop, yspec) < 0)
|
||||
goto done;
|
||||
|
||||
/* Compute canonical namespace context */
|
||||
if (xml_nsctx_yangspec(yspec, &nsc) < 0)
|
||||
goto done;
|
||||
if ((xbot = xpath_first(xtop, nsc, "%s", top_path)) == NULL){
|
||||
fprintf(stderr, "Path not found in top tree: %s\n", top_path);
|
||||
goto done;
|
||||
}
|
||||
xt = xbot;
|
||||
}
|
||||
if (input_filename){
|
||||
if ((fd = open(input_filename, O_RDONLY)) < 0){
|
||||
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
|
||||
|
|
@ -203,11 +290,15 @@ main(int argc,
|
|||
goto done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (xml_parse_file(fd, "</config>", NULL, &xt) < 0){
|
||||
else{ /* XML */
|
||||
if ((ret = xml_parse_file2(fd, (xt==NULL)?YB_TOP:YB_PARENT, yspec, NULL, &xt, &xerr)) < 0){
|
||||
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){
|
||||
clicon_rpc_generate_error(xerr, "util_xml", NULL);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump data structures (for debug) */
|
||||
|
|
@ -220,32 +311,8 @@ main(int argc,
|
|||
|
||||
/* 3. Validate data (if yspec) */
|
||||
if (validate){
|
||||
xc = xml_child_i(xt, 0);
|
||||
/* Populate */
|
||||
if (xml_apply0(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (validate_tree(h, xt, yspec) < 0)
|
||||
goto done;
|
||||
/* Sort */
|
||||
if (xml_apply0(xc, CX_ELMNT, xml_sort, h) < 0)
|
||||
goto done;
|
||||
/* Add default values */
|
||||
if (xml_apply(xc, CX_ELMNT, xml_default, h) < 0)
|
||||
goto done;
|
||||
if (xml_apply0(xc, -1, xml_sort_verify, h) < 0)
|
||||
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
|
||||
if ((ret = xml_yang_validate_all_top(h, xc, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, xc, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if ((cbret = cbuf_new()) ==NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if (netconf_err2cb(xerr, cbret) < 0)
|
||||
goto done;
|
||||
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cbret));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* 4. Output data (xml/json) */
|
||||
if (output){
|
||||
|
|
@ -260,11 +327,17 @@ main(int argc,
|
|||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (nsc)
|
||||
cvec_free(nsc);
|
||||
if (cbret)
|
||||
cbuf_free(cbret);
|
||||
if (xcfg)
|
||||
xml_free(xcfg);
|
||||
if (xt)
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
else if (xt)
|
||||
xml_free(xt);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2020 Olof Hagsand
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
|
|
@ -137,6 +138,8 @@ main(int argc,
|
|||
cvec *nsc = NULL;
|
||||
int canonical = 0;
|
||||
cxobj *xcfg = NULL;
|
||||
cbuf *cbret = NULL;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
|
||||
|
|
@ -277,32 +280,28 @@ main(int argc,
|
|||
* If fd=0, then continue reading from stdin (after CR)
|
||||
* If fd>0, reading from file opened as argv[1]
|
||||
*/
|
||||
if (xml_parse_file(fd, "</clicon>", NULL, &x0) < 0){
|
||||
if (xml_parse_file(fd, NULL, &x0) < 0){
|
||||
fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate XML as well */
|
||||
if (yang_file_dir){
|
||||
cbuf *cbret = NULL;
|
||||
cxobj *x1;
|
||||
cxobj *xerr = NULL; /* malloced must be freed */
|
||||
|
||||
x1 = xml_child_i(x0, 0);
|
||||
/* Populate */
|
||||
if (xml_apply0(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
if (xml_spec_populate(x0, yspec, NULL) < 0)
|
||||
goto done;
|
||||
/* Sort */
|
||||
if (xml_apply0(x1, CX_ELMNT, xml_sort, h) < 0)
|
||||
if (xml_apply(x0, CX_ELMNT, xml_sort, h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Add default values */
|
||||
if (xml_apply(x1, CX_ELMNT, xml_default, h) < 0)
|
||||
if (xml_apply(x0, CX_ELMNT, xml_default, h) < 0)
|
||||
goto done;
|
||||
if (xml_apply0(x1, -1, xml_sort_verify, h) < 0)
|
||||
if (xml_apply0(x0, -1, xml_sort_verify, h) < 0)
|
||||
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
|
||||
if ((ret = xml_yang_validate_all_top(h, x1, &xerr)) < 0)
|
||||
if ((ret = xml_yang_validate_all_top(h, x0, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, x1, &xerr)) < 0)
|
||||
if (ret > 0 && (ret = xml_yang_validate_add(h, x0, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if ((cbret = cbuf_new()) ==NULL){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue