Merge branch 'develop'
This commit is contained in:
commit
157fe0221e
34 changed files with 807 additions and 304 deletions
160
CHANGELOG.md
160
CHANGELOG.md
|
|
@ -1,69 +1,143 @@
|
||||||
# Clixon CHANGELOG
|
# Clixon CHANGELOG
|
||||||
|
|
||||||
- Added new backend plugin callback: "plugin_statedata()" for retreiving state data
|
## Upcoming 3.3.2
|
||||||
|
|
||||||
- Added yang dir with ietf-netconf and clixon-config yang specs for internal usage.
|
### Major changes:
|
||||||
|
* Changed top-level netconf get-config and get to return `<data>` instead of `<data><config>` to comply to the RFC.
|
||||||
|
* If you use direct netconf get or get-config calls, you may need to handle the return XML differently.
|
||||||
|
* RESTCONF and CLI is not affected.
|
||||||
|
* Example:
|
||||||
|
```
|
||||||
|
Query:
|
||||||
|
<rpc><get/></rpc>
|
||||||
|
New reply:
|
||||||
|
<rpc-reply>
|
||||||
|
<data>
|
||||||
|
<a/> # Example data model
|
||||||
|
</data>
|
||||||
|
</rpc-reply>
|
||||||
|
|
||||||
- Added state data: Netconf <get> operation introduced; Error when
|
Old reply:
|
||||||
adding state data in <edit-config>.
|
<rpc-reply>
|
||||||
|
<data>
|
||||||
|
<config> # Removed
|
||||||
|
<a/>
|
||||||
|
</config> # Removed
|
||||||
|
</data>
|
||||||
|
</rpc-reply>
|
||||||
|
```
|
||||||
|
|
||||||
- Fixed bug where cli set of leaf-list were doubled, eg cli set foo -> foofoo
|
* Added support for yang presence and no-presence containers. Previous default was "presence".
|
||||||
|
* Empty containers will be removed unless you have used the "presence" yang declaration.
|
||||||
|
* Example YANG without presence:
|
||||||
|
```
|
||||||
|
container
|
||||||
|
nopresence {
|
||||||
|
leaf j {
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
If you submit "nopresence" without a leaf, it will automatically be removed:
|
||||||
|
```
|
||||||
|
<nopresence/> # removed
|
||||||
|
<nopresence> # not removed
|
||||||
|
<j>hello</j>
|
||||||
|
</nopresence>
|
||||||
|
```
|
||||||
|
|
||||||
- Restricted yang (sub)module file match to match RFC6020 exactly
|
* Added YANG RPC support for netconf and CLI. With example rpc documentation and testcase. This replaces the previous "downcall" mechanism.
|
||||||
|
* This means you can make netconf rpc calls as defined by YANG.
|
||||||
|
* However you need to register an RPC backend callback using the backend_rpc_cb_register() function. See documentation and example for more details.
|
||||||
|
* Note that RESTCONF PUT for RCP calls is not yet supported.
|
||||||
|
* Example, the following YANG RPC definition enables you to run a netconf rpc.
|
||||||
|
```
|
||||||
|
YANG:
|
||||||
|
rpc myrpc {
|
||||||
|
input {
|
||||||
|
leaf name {
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NETCONF:
|
||||||
|
<rpc><myrpc><name>hello</name><rpc>
|
||||||
|
```
|
||||||
|
|
||||||
- Generalized yang type resolution to all included (sub)modules not just the topmost
|
* Enhanced leafref functionality:
|
||||||
|
* Validation for leafref forward and backward references;
|
||||||
|
* CLI completion for generated cli leafrefs for both absolute and relative paths.
|
||||||
|
* Example, relative path:
|
||||||
|
```
|
||||||
|
leaf ifname {
|
||||||
|
type leafref {
|
||||||
|
path "../../interface/name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Generic map_str2int generic mapping tables
|
* Added state data: Netconf `<get>` operation, new backend plugin callback: "plugin_statedata()" for retreiving state data.
|
||||||
|
* You can use netconf: `<rpc><get/></rpc>` and it will return both config and state data.
|
||||||
|
* Restconf GET will return state data also, if defined.
|
||||||
|
* You need to define state data in a backend callback. See the example and documentation for more details.
|
||||||
|
|
||||||
- Removed vector return values from xmldb_get()
|
### Minor changes:
|
||||||
|
* Removed 'margin' parameter of yang_print().
|
||||||
|
* Extended example with ietf-routing (not only ietf-ip) for rpc operations.
|
||||||
|
* Added yang dir with ietf-netconf and clixon-config yang specs for internal usage.
|
||||||
|
* Fixed bug where cli set of leaf-list were doubled, eg cli set foo -> foofoo
|
||||||
|
* Restricted yang (sub)module file match to match RFC6020 exactly
|
||||||
|
* Generic map_str2int generic mapping tables
|
||||||
|
* Removed vector return values from xmldb_get()
|
||||||
|
* Generalized yang type resolution to all included (sub)modules not just the topmost
|
||||||
|
|
||||||
## 3.3.1 June 7 2017
|
## 3.3.1 June 7 2017
|
||||||
|
|
||||||
- Fixed yang leafref cli completion.
|
* Fixed yang leafref cli completion for absolute paths.
|
||||||
|
|
||||||
- Removed non-standard api_path extension from the internal netconf protocol so that the internal netcinf is now fully standard.
|
* Removed non-standard api_path extension from the internal netconf protocol so that the internal netconf is now fully standard.
|
||||||
|
|
||||||
- Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
|
* Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000
|
||||||
|
|
||||||
## 3.3.0
|
## 3.3.0
|
||||||
|
|
||||||
May 2017
|
May 2017
|
||||||
|
|
||||||
- Datastore text module is now default.
|
* Datastore text module is now default.
|
||||||
|
|
||||||
- Refined netconf "none" semantics in tests and text datastore
|
* Refined netconf "none" semantics in tests and text datastore
|
||||||
|
|
||||||
- Moved apps/dbctrl to datastore/
|
* Moved apps/dbctrl to datastore/
|
||||||
|
|
||||||
- Added connect/disconnect/getopt/setopt and handle to xmldb API
|
* Added connect/disconnect/getopt/setopt and handle to xmldb API
|
||||||
|
|
||||||
- Added datastore 'text'
|
* Added datastore 'text'
|
||||||
|
|
||||||
- Configure (autoconf) changes
|
* Configure (autoconf) changes
|
||||||
Removed libcurl dependency
|
Removed libcurl dependency
|
||||||
Disable restconf (and fastcgi) with configure --disable-restconf
|
Disable restconf (and fastcgi) with configure --disable-restconf
|
||||||
Disable keyvalue datastore (and qdbm) with configure --disable-keyvalue
|
Disable keyvalue datastore (and qdbm) with configure --disable-keyvalue
|
||||||
|
|
||||||
- Created xmldb plugin api
|
* Created xmldb plugin api
|
||||||
Moved qdbm, chunk and xmldb to datastore keyvalue directories
|
Moved qdbm, chunk and xmldb to datastore keyvalue directories
|
||||||
Removed all other clixon dependency on chunk code
|
Removed all other clixon dependency on chunk code
|
||||||
|
|
||||||
- cli_copy_config added as generic cli command
|
* cli_copy_config added as generic cli command
|
||||||
- cli_show_config added as generic cli command
|
* cli_show_config added as generic cli command
|
||||||
Replace all show_confv*() and show_conf*() with cli_show_config()
|
Replace all show_confv*() and show_conf*() with cli_show_config()
|
||||||
Example: replace:
|
Example: replace:
|
||||||
show_confv_as_json("candidate","/sender");
|
show_confv_as_json("candidate","/sender");
|
||||||
with:
|
with:
|
||||||
cli_show_config("candidate","json","/sender");
|
cli_show_config("candidate","json","/sender");
|
||||||
- Alternative yang spec option -y added to all applications
|
* Alternative yang spec option -y added to all applications
|
||||||
- Many clicon special string functions have been removed
|
* Many clicon special string functions have been removed
|
||||||
- The netconf support has been extended with lock/unlock
|
* The netconf support has been extended with lock/unlock
|
||||||
- clicon_rpc_call() has been removed and should be replaced by extending the
|
* clicon_rpc_call() has been removed and should be replaced by extending the
|
||||||
internal netconf protocol.
|
internal netconf protocol.
|
||||||
See downcall() function in example/routing_cli.c and
|
See downcall() function in example/routing_cli.c and
|
||||||
routing_downcall() in example/routing_backend.c
|
routing_downcall() in example/routing_backend.c
|
||||||
- Replace clicon_rpc_xmlput with clicon_rpc_edit_config
|
* Replace clicon_rpc_xmlput with clicon_rpc_edit_config
|
||||||
- Removed xmldb daemon. All xmldb acceses is made backend daemon.
|
* Removed xmldb daemon. All xmldb acceses is made backend daemon.
|
||||||
No direct accesses by clients to xmldb API.
|
No direct accesses by clients to xmldb API.
|
||||||
Instead use the rpc calls in clixon_proto_client.[ch]
|
Instead use the rpc calls in clixon_proto_client.[ch]
|
||||||
In clients (eg cli/netconf) replace xmldb_get() in client code with
|
In clients (eg cli/netconf) replace xmldb_get() in client code with
|
||||||
|
|
@ -74,48 +148,48 @@ May 2017
|
||||||
clicon_rpc_get_config(h, dbstr, api_path, &xt);
|
clicon_rpc_get_config(h, dbstr, api_path, &xt);
|
||||||
xpath_vec(xt, api_path, &xvec, &xlen)
|
xpath_vec(xt, api_path, &xvec, &xlen)
|
||||||
|
|
||||||
- clicon_rpc_change() is replaced with clicon_rpc_edit_config().
|
* clicon_rpc_change() is replaced with clicon_rpc_edit_config().
|
||||||
Note modify argument 5:
|
Note modify argument 5:
|
||||||
clicon_rpc_change(h, db, op, apipath, "value")
|
clicon_rpc_change(h, db, op, apipath, "value")
|
||||||
to:
|
to:
|
||||||
clicon_rpc_edit_config(h, db, op, apipath, "<config>value</config>")
|
clicon_rpc_edit_config(h, db, op, apipath, `"<config>value</config>"`)
|
||||||
|
|
||||||
- xmdlb_put_xkey() and xmldb_put_tree() have been folded into xmldb_put()
|
* xmdlb_put_xkey() and xmldb_put_tree() have been folded into xmldb_put()
|
||||||
Replace xmldb_put_xkey with xmldb_put as follows:
|
Replace xmldb_put_xkey with xmldb_put as follows:
|
||||||
xmldb_put_xkey(h, "candidate", cbuf_get(cb), str, OP_REPLACE);
|
xmldb_put_xkey(h, "candidate", cbuf_get(cb), str, OP_REPLACE);
|
||||||
with
|
with
|
||||||
clicon_xml_parse(&xml, "<config>%s</config>", str);
|
clicon_xml_parse(&xml, `"<config>%s</config>"`, str);
|
||||||
xmldb_put(h, "candidate", OP_REPLACE, cbuf_get(cb), xml);
|
xmldb_put(h, "candidate", OP_REPLACE, cbuf_get(cb), xml);
|
||||||
xml_free(xml);
|
xml_free(xml);
|
||||||
|
|
||||||
- Change internal protocol from clicon_proto.h to netconf.
|
* Change internal protocol from clicon_proto.h to netconf.
|
||||||
This means that the internal protocol defined in clixon_proto.[ch] is removed
|
This means that the internal protocol defined in clixon_proto.[ch] is removed
|
||||||
|
|
||||||
- Netconf startup configuration support. Set CLICON_USE_STARTUP_CONFIG to 1 to
|
* Netconf startup configuration support. Set CLICON_USE_STARTUP_CONFIG to 1 to
|
||||||
enable. Eg, if backend_main is started with -CIr startup will be copied to
|
enable. Eg, if backend_main is started with -CIr startup will be copied to
|
||||||
running.
|
running.
|
||||||
|
|
||||||
- Added ".." as valid step in xpath
|
* Added ".." as valid step in xpath
|
||||||
|
|
||||||
- Use restconf format for internal xmldb keys. Eg /a/b=3,4
|
* Use restconf format for internal xmldb keys. Eg /a/b=3,4
|
||||||
|
|
||||||
- List keys with special characters RFC 3986 encoded.
|
* List keys with special characters RFC 3986 encoded.
|
||||||
|
|
||||||
- Replaced cli expand functions with single to multiple args
|
* Replaced cli expand functions with single to multiple args
|
||||||
This change is _not_ backward compatible
|
This change is _not_ backward compatible
|
||||||
This effects all calls to expand_dbvar() or user-defined
|
This effects all calls to expand_dbvar() or user-defined
|
||||||
expand callbacks
|
expand callbacks
|
||||||
|
|
||||||
- Replaced cli callback functions with single arg to multiple args
|
* Replaced cli callback functions with single arg to multiple args
|
||||||
This change is _not_ backward compatible.
|
This change is _not_ backward compatible.
|
||||||
You are affected if you
|
You are affected if you
|
||||||
(1) use system callbacks (i.e. in clixon_cli_api.h)
|
(1) use system callbacks (i.e. in clixon_cli_api.h)
|
||||||
(2) write your own cli callbacks
|
(2) write your own cli callbacks
|
||||||
|
|
||||||
If you use cli callbacks, you need to rewrite cli callbacks from eg:
|
If you use cli callbacks, you need to rewrite cli callbacks from eg:
|
||||||
load("Comment") <filename:string>,load_config_file("filename replace");
|
`load("Comment") <filename:string>,load_config_file("filename replace");`
|
||||||
to:
|
to:
|
||||||
load("Comment") <filename:string>,load_config_file("filename", "replace");
|
`load("Comment") <filename:string>,load_config_file("filename", "replace");`
|
||||||
|
|
||||||
If you write your own, you need to change the callback signature from;
|
If you write your own, you need to change the callback signature from;
|
||||||
int cli_callback(clicon_handle h, cvec *vars, cg_var *arg)
|
int cli_callback(clicon_handle h, cvec *vars, cg_var *arg)
|
||||||
|
|
@ -128,9 +202,9 @@ May 2017
|
||||||
load_config_file, save_config_file, delete_all, discard_changes, cli_notify,
|
load_config_file, save_config_file, delete_all, discard_changes, cli_notify,
|
||||||
show_yang, show_conf_xpath
|
show_yang, show_conf_xpath
|
||||||
|
|
||||||
- Added --with-cligen and --with-qdbm configure options
|
* Added --with-cligen and --with-qdbm configure options
|
||||||
- Added union type check for non-cli (eg xml) input
|
* Added union type check for non-cli (eg xml) input
|
||||||
- Empty yang type. Relaxed yang types for unions, eg two strings with different length.
|
* Empty yang type. Relaxed yang types for unions, eg two strings with different length.
|
||||||
|
|
||||||
Dec 2016: Dual license: both GPLv3 and APLv2
|
Dec 2016: Dual license: both GPLv3 and APLv2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ Table of contents
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
=============
|
=============
|
||||||
- [Frequently asked questions](doc/FAQ.md)
|
- [Frequently asked questions](doc/FAQ.md
|
||||||
|
- [CHANGELOG](CHANGELOG.md) recent changes.
|
||||||
- [XML datastore](datastore/README.md)
|
- [XML datastore](datastore/README.md)
|
||||||
- [Netconf support](apps/netconf/README.md)
|
- [Netconf support](apps/netconf/README.md)
|
||||||
- [Restconf support](apps/restconf/README.md)
|
- [Restconf support](apps/restconf/README.md)
|
||||||
|
|
@ -58,7 +59,7 @@ Licenses
|
||||||
Clixon is dual license. Either Apache License, Version 2.0 or GNU
|
Clixon is dual license. Either Apache License, Version 2.0 or GNU
|
||||||
General Public License Version 2. You choose.
|
General Public License Version 2. You choose.
|
||||||
|
|
||||||
See [LICENSE.md](LICENSE.md) for license, [CHANGELOG](CHANGELOG.md) for recent changes.
|
See [LICENSE.md](LICENSE.md) for license.
|
||||||
|
|
||||||
Background
|
Background
|
||||||
==========
|
==========
|
||||||
|
|
@ -90,7 +91,7 @@ The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented w
|
||||||
- object-references
|
- object-references
|
||||||
- if-feature
|
- if-feature
|
||||||
- unique
|
- unique
|
||||||
- rpc
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -241,16 +241,16 @@ from_client_get_config(clicon_handle h,
|
||||||
"</rpc-error></rpc-reply>");
|
"</rpc-error></rpc-reply>");
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
cprintf(cbret, "<rpc-reply><data>");
|
cprintf(cbret, "<rpc-reply>");
|
||||||
if (xret!=NULL){
|
if (xret==NULL)
|
||||||
if (xml_child_nr(xret)){
|
cprintf(cbret, "<data/>");
|
||||||
if (xml_name_set(xret, "config") < 0)
|
else{
|
||||||
goto done;
|
if (xml_name_set(xret, "data") < 0)
|
||||||
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
goto done;
|
||||||
goto done;
|
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
||||||
}
|
goto done;
|
||||||
}
|
}
|
||||||
cprintf(cbret, "</data></rpc-reply>");
|
cprintf(cbret, "</rpc-reply>");
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -293,17 +293,16 @@ from_client_get(clicon_handle h,
|
||||||
assert(xret);
|
assert(xret);
|
||||||
if (backend_statedata_call(h, selector, xret) < 0)
|
if (backend_statedata_call(h, selector, xret) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
cprintf(cbret, "<rpc-reply><data>");
|
cprintf(cbret, "<rpc-reply>");
|
||||||
/* if empty only <config/> */
|
if (xret==NULL)
|
||||||
if (xret!=NULL){
|
cprintf(cbret, "<data/>");
|
||||||
if (xml_child_nr(xret)){
|
else{
|
||||||
if (xml_name_set(xret, "config") < 0)
|
if (xml_name_set(xret, "data") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cprintf(cbret, "</data></rpc-reply>");
|
cprintf(cbret, "</rpc-reply>");
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
|
||||||
|
|
@ -89,15 +89,19 @@ generic_validate(yang_spec *yspec,
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *x1;
|
cxobj *x1;
|
||||||
cxobj *x2;
|
cxobj *x2;
|
||||||
int i;
|
|
||||||
yang_stmt *ys;
|
yang_stmt *ys;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* All entries */
|
||||||
|
if (xml_apply(td->td_target, CX_ELMNT,
|
||||||
|
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
/* changed entries */
|
/* changed entries */
|
||||||
for (i=0; i<td->td_clen; i++){
|
for (i=0; i<td->td_clen; i++){
|
||||||
x1 = td->td_scvec[i]; /* source changed */
|
x1 = td->td_scvec[i]; /* source changed */
|
||||||
x2 = td->td_tcvec[i]; /* target changed */
|
x2 = td->td_tcvec[i]; /* target changed */
|
||||||
ys = xml_spec(x1);
|
if (xml_yang_validate_add(x2, NULL) < 0)
|
||||||
if (xml_yang_validate(x2, ys) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* deleted entries */
|
/* deleted entries */
|
||||||
|
|
@ -113,11 +117,8 @@ generic_validate(yang_spec *yspec,
|
||||||
/* added entries */
|
/* added entries */
|
||||||
for (i=0; i<td->td_alen; i++){
|
for (i=0; i<td->td_alen; i++){
|
||||||
x2 = td->td_avec[i];
|
x2 = td->td_avec[i];
|
||||||
ys = xml_spec(x2);
|
if (xml_apply0(x2, CX_ELMNT,
|
||||||
if (xml_yang_validate(x2, ys) < 0)
|
(xml_applyfn_t*)xml_yang_validate_add, NULL) < 0)
|
||||||
goto done;
|
|
||||||
if (xml_apply(x2, CX_ELMNT,
|
|
||||||
(xml_applyfn_t*)xml_yang_validate, NULL) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
|
||||||
|
|
@ -275,9 +275,10 @@ yang2cli_var_sub(clicon_handle h,
|
||||||
if (helptext)
|
if (helptext)
|
||||||
cprintf(cb0, "(\"%s\")", helptext);
|
cprintf(cb0, "(\"%s\")", helptext);
|
||||||
if (completion){
|
if (completion){
|
||||||
|
#if 0
|
||||||
if (type && (strcmp(type, "leafref") == 0)){
|
if (type && (strcmp(type, "leafref") == 0)){
|
||||||
yang_stmt *ypath;
|
yang_stmt *ypath;
|
||||||
|
/* XXX only for absolute xpath */
|
||||||
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||||
clicon_err(OE_XML, 0, "leafref should have path sub");
|
clicon_err(OE_XML, 0, "leafref should have path sub");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -290,6 +291,7 @@ yang2cli_var_sub(clicon_handle h,
|
||||||
ypath->ys_argument);
|
ypath->ys_argument);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
if (cli_expand_var_generate(h, ys, cvtype, cb0,
|
||||||
options, fraction_digits) < 0)
|
options, fraction_digits) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,8 @@ main(int argc, char **argv)
|
||||||
char *dir = dirname(str);
|
char *dir = dirname(str);
|
||||||
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
|
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
|
||||||
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg));
|
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg));
|
||||||
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
|
clicon_option_str_set(h, "CLICON_YANG_DIR", dir);
|
||||||
|
free(str);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -112,12 +112,24 @@ expand_dbvar(void *h,
|
||||||
int j;
|
int j;
|
||||||
int k;
|
int k;
|
||||||
cg_var *cv;
|
cg_var *cv;
|
||||||
|
yang_spec *yspec;
|
||||||
|
cxobj *xtop = NULL; /* xpath root */
|
||||||
|
cxobj *xbot = NULL; /* xpath, NULL if datastore */
|
||||||
|
yang_node *y = NULL; /* yang spec of xpath */
|
||||||
|
yang_stmt *ytype;
|
||||||
|
yang_stmt *ypath;
|
||||||
|
cxobj *xcur;
|
||||||
|
char *xpathcur;
|
||||||
|
|
||||||
if (argv == NULL || cvec_len(argv) != 2){
|
if (argv == NULL || cvec_len(argv) != 2){
|
||||||
clicon_err(OE_PLUGIN, 0, "%s: requires arguments: <db> <xmlkeyfmt>",
|
clicon_err(OE_PLUGIN, 0, "%s: requires arguments: <db> <xmlkeyfmt>",
|
||||||
__FUNCTION__);
|
__FUNCTION__);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||||
|
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
if ((cv = cvec_i(argv, 0)) == NULL){
|
if ((cv = cvec_i(argv, 0)) == NULL){
|
||||||
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <db>");
|
clicon_err(OE_PLUGIN, 0, "%s: Error when accessing argument <db>");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -147,11 +159,40 @@ expand_dbvar(void *h,
|
||||||
clicon_rpc_generate_error(xerr);
|
clicon_rpc_generate_error(xerr);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
xcur = xt; /* default top-of-tree */
|
||||||
|
xpathcur = xpath;
|
||||||
|
/* Create config top-of-tree */
|
||||||
|
if ((xtop = xml_new("config", NULL)) == NULL)
|
||||||
|
goto done;
|
||||||
|
xbot = xtop;
|
||||||
|
if (api_path && api_path2xml(api_path, yspec, xtop, &xbot, &y) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Special case for leafref. Detect leafref via Yang-type,
|
||||||
|
* Get Yang path element, tentatively add the new syntax to the whole
|
||||||
|
* tree and apply the path to that.
|
||||||
|
* Last, the reference point for the xpath code below is changed to
|
||||||
|
* the point of the tentative new xml.
|
||||||
|
* Here the whole syntax tree is loaded, and it would be better to offload
|
||||||
|
* such operations to the datastore by a generic xpath function.
|
||||||
|
*/
|
||||||
|
if ((ytype = yang_find((yang_node*)y, Y_TYPE, NULL)) != NULL)
|
||||||
|
if (strcmp(ytype->ys_argument, "leafref")==0){
|
||||||
|
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||||
|
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
xpathcur = ypath->ys_argument;
|
||||||
|
if (xml_merge(xt, xtop, yspec) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((xcur = xpath_first(xt, xpath)) == NULL){
|
||||||
|
clicon_err(OE_DB, 0, "xpath %s should return merged content", xpath);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* One round to detect duplicates
|
/* One round to detect duplicates
|
||||||
* XXX The code below would benefit from some cleanup
|
|
||||||
*/
|
*/
|
||||||
j = 0;
|
j = 0;
|
||||||
if (xpath_vec(xt, xpath, &xvec, &xlen) < 0)
|
if (xpath_vec(xcur, xpathcur, &xvec, &xlen) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
for (i = 0; i < xlen; i++) {
|
for (i = 0; i < xlen; i++) {
|
||||||
char *str;
|
char *str;
|
||||||
|
|
@ -194,6 +235,8 @@ expand_dbvar(void *h,
|
||||||
done:
|
done:
|
||||||
if (xvec)
|
if (xvec)
|
||||||
free(xvec);
|
free(xvec);
|
||||||
|
if (xtop)
|
||||||
|
xml_free(xtop);
|
||||||
if (xt)
|
if (xt)
|
||||||
xml_free(xt);
|
xml_free(xt);
|
||||||
if (xpath)
|
if (xpath)
|
||||||
|
|
@ -344,7 +387,7 @@ show_yang(clicon_handle h,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
yn = (yang_node*)yspec;
|
yn = (yang_node*)yspec;
|
||||||
yang_print(stdout, yn, 0);
|
yang_print(stdout, yn);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int show_yangv(clicon_handle h, cvec *vars, cvec *argv)
|
int show_yangv(clicon_handle h, cvec *vars, cvec *argv)
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,28 @@ catch:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Struct to carry info into and out of ys_find_rpc callback
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char *name; /* name of rpc */
|
||||||
|
yang_stmt *yrpc; /* matching yang statement */
|
||||||
|
} find_rpc_arg;
|
||||||
|
|
||||||
|
/*! Check yang rpc statement, return yang rpc statement if found
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ys_find_rpc(yang_stmt *ys,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
find_rpc_arg *fra = (find_rpc_arg*)arg;
|
||||||
|
|
||||||
|
if (strcmp(fra->name, ys->ys_argument) == 0){
|
||||||
|
fra->yrpc = ys;
|
||||||
|
return 1; /* handled */
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*! See if there is any callback registered for this tag
|
/*! See if there is any callback registered for this tag
|
||||||
*
|
*
|
||||||
* @param[in] h clicon handle
|
* @param[in] h clicon handle
|
||||||
|
|
@ -211,21 +233,73 @@ netconf_plugin_callbacks(clicon_handle h,
|
||||||
cxobj *xn,
|
cxobj *xn,
|
||||||
cxobj **xret)
|
cxobj **xret)
|
||||||
{
|
{
|
||||||
|
int retval = -1;
|
||||||
netconf_reg_t *nreg;
|
netconf_reg_t *nreg;
|
||||||
int retval;
|
yang_spec *yspec;
|
||||||
|
yang_stmt *yrpc;
|
||||||
|
yang_stmt *yinput;
|
||||||
|
yang_stmt *youtput;
|
||||||
|
cxobj *xoutput;
|
||||||
|
find_rpc_arg fra = {0,0};
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (deps == NULL)
|
if (deps != NULL){
|
||||||
return 0;
|
nreg = deps;
|
||||||
nreg = deps;
|
do {
|
||||||
do {
|
if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){
|
||||||
if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){
|
if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0)
|
||||||
if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0)
|
goto done;
|
||||||
return -1;
|
retval = 1; /* handled */
|
||||||
else
|
goto done;
|
||||||
return 1; /* handled */
|
}
|
||||||
|
nreg = NEXTQ(netconf_reg_t *, nreg);
|
||||||
|
} while (nreg != deps);
|
||||||
|
}
|
||||||
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||||
|
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Find yang rpc statement, return yang rpc statement if found */
|
||||||
|
fra.name = xml_name(xn);
|
||||||
|
if ((ret = yang_apply((yang_node*)yspec, Y_RPC, ys_find_rpc, &fra)) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Check if found */
|
||||||
|
if (ret == 1){
|
||||||
|
yrpc = fra.yrpc;
|
||||||
|
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
|
||||||
|
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
|
||||||
|
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(xn, CX_ELMNT,
|
||||||
|
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_yang_validate_add(xn, NULL) < 0)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
nreg = NEXTQ(netconf_reg_t *, nreg);
|
/*
|
||||||
} while (nreg != deps);
|
* 1. Check xn arguments with input statement.
|
||||||
return 0;
|
* 2. Send to backend as clicon_msg-encode()
|
||||||
|
* 3. In backend to similar but there call actual backend
|
||||||
|
*/
|
||||||
|
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Sanity check of outgoing XML */
|
||||||
|
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){
|
||||||
|
xoutput=xpath_first(*xret, "/");
|
||||||
|
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
|
||||||
|
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_apply(xoutput, CX_ELMNT,
|
||||||
|
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xml_yang_validate_add(xoutput, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
retval = 1; /* handled by callback */
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ netconf_get_config(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (xfilter &&
|
if (xfilter &&
|
||||||
(xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL &&
|
(xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL &&
|
||||||
(xconf = xpath_first(*xret, "/rpc-reply/data/configuration")) != NULL){
|
(xconf = xpath_first(*xret, "/rpc-reply/data")) != NULL){
|
||||||
/* xml_filter removes parts of xml tree not matching */
|
/* xml_filter removes parts of xml tree not matching */
|
||||||
if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) ||
|
if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) ||
|
||||||
xml_filter(xfilterconf, xconf) < 0){
|
xml_filter(xfilterconf, xconf) < 0){
|
||||||
|
|
@ -571,7 +571,7 @@ netconf_get(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (xfilter &&
|
if (xfilter &&
|
||||||
(xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL &&
|
(xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL &&
|
||||||
(xconf = xpath_first(*xret, "/rpc-reply/data/configuration")) != NULL){
|
(xconf = xpath_first(*xret, "/rpc-reply/data")) != NULL){
|
||||||
/* xml_filter removes parts of xml tree not matching */
|
/* xml_filter removes parts of xml tree not matching */
|
||||||
if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) ||
|
if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) ||
|
||||||
xml_filter(xfilterconf, xconf) < 0){
|
xml_filter(xfilterconf, xconf) < 0){
|
||||||
|
|
|
||||||
|
|
@ -673,7 +673,7 @@ put(char *dbfile,
|
||||||
clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument);
|
clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument);
|
||||||
if (debug){
|
if (debug){
|
||||||
xml_print(stderr, xt);
|
xml_print(stderr, xt);
|
||||||
// yang_print(stderr, (yang_node*)ys, 0);
|
// yang_print(stderr, (yang_node*)ys);
|
||||||
}
|
}
|
||||||
if ((opstr = xml_find_value(xt, "operation")) != NULL)
|
if ((opstr = xml_find_value(xt, "operation")) != NULL)
|
||||||
if (xml_operation(opstr, &op) < 0)
|
if (xml_operation(opstr, &op) < 0)
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,50 @@ text_modify_top(cxobj *x0,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! For containers without presence and no children, remove
|
||||||
|
* @param[in] x XML tree node
|
||||||
|
* @note This should really be unnecessary since yspec should be set on creation
|
||||||
|
* @code
|
||||||
|
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
|
||||||
|
* @endcode
|
||||||
|
* See section 7.5.1 in rfc6020bis-02.txt:
|
||||||
|
* No presence:
|
||||||
|
* those that exist only for organizing the hierarchy of data nodes:
|
||||||
|
* the container has no meaning of its own, existing
|
||||||
|
* only to contain child nodes. This is the default style.
|
||||||
|
* (Remove these if no children)
|
||||||
|
* Presence:
|
||||||
|
* the presence of the container itself is
|
||||||
|
* configuration data, representing a single bit of configuration data.
|
||||||
|
* The container acts as both a configuration knob and a means of
|
||||||
|
* organizing related configuration. These containers are explicitly
|
||||||
|
* created and deleted.
|
||||||
|
* (Dont touch these)
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
xml_container_presence(cxobj *x,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *name;
|
||||||
|
yang_stmt *y; /* yang node */
|
||||||
|
|
||||||
|
name = xml_name(x);
|
||||||
|
if ((y = (yang_stmt*)xml_spec(x)) == NULL){
|
||||||
|
clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, name);
|
||||||
|
retval = 0;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Mark node that is: container, have no children, dont have presence */
|
||||||
|
if (y->ys_keyword == Y_CONTAINER &&
|
||||||
|
xml_child_nr(x)==0 &&
|
||||||
|
yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL)
|
||||||
|
xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Modify database provided an xml tree and an operation
|
/*! Modify database provided an xml tree and an operation
|
||||||
* This is a clixon datastore plugin of the the xmldb api
|
* This is a clixon datastore plugin of the the xmldb api
|
||||||
* @see xmldb_put
|
* @see xmldb_put
|
||||||
|
|
@ -745,6 +789,12 @@ text_put(xmldb_handle xh,
|
||||||
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||||
(void*)XML_FLAG_NONE) < 0)
|
(void*)XML_FLAG_NONE) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
/* Mark non-presence containers that do not have children */
|
||||||
|
if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_container_presence, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Remove (prune) nodes that are marked (non-presence containers w/o children) */
|
||||||
|
if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0)
|
||||||
|
goto done;
|
||||||
// output:
|
// output:
|
||||||
/* Print out top-level xml tree after modification to file */
|
/* Print out top-level xml tree after modification to file */
|
||||||
if ((cb = cbuf_new()) == NULL){
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
|
|
||||||
44
doc/FAQ.md
44
doc/FAQ.md
|
|
@ -197,8 +197,10 @@ They are documented in [CLIgen tutorial](https://github.com/olofhagsand/cligen/b
|
||||||
## How do I write a validation function?
|
## How do I write a validation function?
|
||||||
Similar to a commit function, but instead write the transaction_validate() function.
|
Similar to a commit function, but instead write the transaction_validate() function.
|
||||||
Check for inconsistencies in the XML trees and if they fail, make an clicon_err() call.
|
Check for inconsistencies in the XML trees and if they fail, make an clicon_err() call.
|
||||||
|
```
|
||||||
clicon_err(OE_PLUGIN, 0, "Route %s lacks ipv4 addr", name);
|
clicon_err(OE_PLUGIN, 0, "Route %s lacks ipv4 addr", name);
|
||||||
return -1;
|
return -1;
|
||||||
|
```
|
||||||
The validation or commit will then be aborted.
|
The validation or commit will then be aborted.
|
||||||
|
|
||||||
## How do I write a state data callback function?
|
## How do I write a state data callback function?
|
||||||
|
|
@ -210,3 +212,45 @@ To return state data, you need to write a backend state data callback
|
||||||
with the name "plugin_statedata()" where you return an XML tree.
|
with the name "plugin_statedata()" where you return an XML tree.
|
||||||
|
|
||||||
Please look at the example for an example on how to write a state data callback.
|
Please look at the example for an example on how to write a state data callback.
|
||||||
|
|
||||||
|
## How do I write an RPC function?
|
||||||
|
|
||||||
|
A YANG RPC is an application specific operation. Example:
|
||||||
|
```
|
||||||
|
rpc fib-route {
|
||||||
|
input {
|
||||||
|
leaf inarg { type string; }
|
||||||
|
}
|
||||||
|
output {
|
||||||
|
leaf outarg { type string; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
which defines the fib-route operation present in the example (the arguments have been changed).
|
||||||
|
|
||||||
|
Clixon automatically relays the RPC to the clixon backend. To
|
||||||
|
implement the RFC, you need to register an RPC callback in the backend plugin:
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
int
|
||||||
|
plugin_init(clicon_handle h)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
backend_rpc_cb_register(h, fib_route, NULL, "fib-route");
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
And then define the callback itself:
|
||||||
|
```
|
||||||
|
static int
|
||||||
|
fib_route(clicon_handle h, /* Clicon handle */
|
||||||
|
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||||
|
struct client_entry *ce, /* Client session */
|
||||||
|
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||||
|
void *arg) /* Argument given at register */
|
||||||
|
{
|
||||||
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Here, the callback is over-simplified.
|
||||||
|
|
@ -67,6 +67,7 @@ YANGSPECS += ietf-routing@2014-10-26.yang
|
||||||
YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang
|
YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang
|
||||||
YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang
|
YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang
|
||||||
YANGSPECS += ietf-ipsec@2016-03-09.yang
|
YANGSPECS += ietf-ipsec@2016-03-09.yang
|
||||||
|
YANGSPECS += example.yang
|
||||||
|
|
||||||
# Backend plugin
|
# Backend plugin
|
||||||
BE_SRC = routing_backend.c
|
BE_SRC = routing_backend.c
|
||||||
|
|
|
||||||
|
|
@ -65,22 +65,52 @@ Routing notification
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Extending
|
## Operation data
|
||||||
|
|
||||||
Clixon has an extension mechanism which can be used to make extended internal
|
Clixon implements Yang RPC operations by an extension mechanism. The
|
||||||
netconf messages to the backend configuration engine. You may need this to
|
extension mechanism enables you to add application-specific
|
||||||
make some special operation that is not covered by standard
|
operations. It works by adding user-defined callbacks for added
|
||||||
netconf functions. The example has a simple "echo" downcall
|
netconf operations. It is possible to use the extension mechanism
|
||||||
mechanism that simply echoes what is sent down and is included for
|
independent of the yang rpc construct, but it is recommended to use
|
||||||
reference. A more realistic downcall would perform some action, such as
|
that, and the example includes such an example:
|
||||||
reading some status.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
cli> downcall "This is a string"
|
cli> rpc ipv4
|
||||||
This is a string
|
<rpc-reply>
|
||||||
|
<ok/>
|
||||||
|
</rpc-reply>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function).
|
||||||
|
```
|
||||||
|
<rpc>
|
||||||
|
<fib-route>
|
||||||
|
<routing-instance-name>ipv4</routing-instance-name>
|
||||||
|
</fib-route>
|
||||||
|
</rpc>
|
||||||
|
```
|
||||||
|
|
||||||
|
The backend in turn registers a callback (fib_route()) which handles the RPC.
|
||||||
|
```
|
||||||
|
static int
|
||||||
|
fib_route(clicon_handle h,
|
||||||
|
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||||
|
struct client_entry *ce, /* Client session */
|
||||||
|
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||||
|
void *arg) /* Argument given at register */
|
||||||
|
{
|
||||||
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int
|
||||||
|
plugin_init(clicon_handle h)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
backend_rpc_cb_register(h, fib_route, NULL, "fib-route");
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
## State data
|
## State data
|
||||||
|
|
||||||
Netconf <get> and restconf GET also returns state data, in contrast to
|
Netconf <get> and restconf GET also returns state data, in contrast to
|
||||||
|
|
|
||||||
10
example/example.yang
Normal file
10
example/example.yang
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
module example {
|
||||||
|
import ietf-ip {
|
||||||
|
prefix ip;
|
||||||
|
}
|
||||||
|
import ietf-routing {
|
||||||
|
prefix rt;
|
||||||
|
}
|
||||||
|
description
|
||||||
|
"Example code that includes ietf-ip and ietf-routing";
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,12 @@ CLICON_CLI_MODE routing
|
||||||
|
|
||||||
# Option used to construct initial yang file:
|
# Option used to construct initial yang file:
|
||||||
# <module>[@<revision>]
|
# <module>[@<revision>]
|
||||||
CLICON_YANG_MODULE_MAIN ietf-ip
|
#CLICON_YANG_MODULE_MAIN ietf-ip
|
||||||
|
CLICON_YANG_MODULE_MAIN example
|
||||||
|
|
||||||
# Option used to construct initial yang file:
|
# Option used to construct initial yang file:
|
||||||
# <module>[@<revision>]
|
# <module>[@<revision>]
|
||||||
CLICON_YANG_MODULE_REVISION 2014-06-16
|
#CLICON_YANG_MODULE_REVISION 2014-06-16
|
||||||
|
|
||||||
# Generate code for CLI completion of existing db symbols
|
# Generate code for CLI completion of existing db symbols
|
||||||
# CLICON_CLI_GENMODEL_COMPLETION 0
|
# CLICON_CLI_GENMODEL_COMPLETION 0
|
||||||
|
|
|
||||||
|
|
@ -119,14 +119,30 @@ notification_timer_setup(clicon_handle h)
|
||||||
return event_reg_timeout(t, notification_timer, h, "notification timer");
|
return event_reg_timeout(t, notification_timer, h, "notification timer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! IETF Routing fib-route rpc */
|
||||||
static int
|
static int
|
||||||
routing_downcall(clicon_handle h,
|
fib_route(clicon_handle h, /* Clicon handle */
|
||||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||||
struct client_entry *ce, /* Client session */
|
struct client_entry *ce, /* Client session */
|
||||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||||
void *arg) /* Argument given at register */
|
void *arg) /* Argument given at register */
|
||||||
{
|
{
|
||||||
cprintf(cbret, "<rpc-reply><ok>%s</ok></rpc-reply>", xml_body(xe));
|
cprintf(cbret, "<rpc-reply><route>"
|
||||||
|
"<address-family>ipv4</address-family>"
|
||||||
|
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
|
||||||
|
"</route></rpc-reply>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! IETF Routing route-count rpc */
|
||||||
|
static int
|
||||||
|
route_count(clicon_handle h,
|
||||||
|
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||||
|
struct client_entry *ce, /* Client session */
|
||||||
|
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||||
|
void *arg) /* Argument given at register */
|
||||||
|
{
|
||||||
|
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,10 +189,15 @@ plugin_init(clicon_handle h)
|
||||||
|
|
||||||
if (notification_timer_setup(h) < 0)
|
if (notification_timer_setup(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Register callback for netconf application-specific rpc call */
|
/* Register callback for routing rpc calls */
|
||||||
if (backend_rpc_cb_register(h, routing_downcall,
|
if (backend_rpc_cb_register(h, fib_route,
|
||||||
NULL,
|
NULL,
|
||||||
"myrouting"/* Xml tag when callback is made */
|
"fib-route"/* Xml tag when callback is made */
|
||||||
|
) < 0)
|
||||||
|
goto done;
|
||||||
|
if (backend_rpc_cb_register(h, route_count,
|
||||||
|
NULL,
|
||||||
|
"route-count"/* Xml tag when callback is made */
|
||||||
) < 0)
|
) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
|
||||||
|
|
@ -92,40 +92,36 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! get argument and send as string to backend as RPC (which returns the string)
|
/*! Example "downcall": ietf-routing fib-route RPC */
|
||||||
*/
|
|
||||||
int
|
int
|
||||||
downcall(clicon_handle h,
|
fib_route_rpc(clicon_handle h,
|
||||||
cvec *vars,
|
cvec *cvv,
|
||||||
cvec *argv)
|
cvec *argv)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
struct clicon_msg *msg = NULL;
|
cg_var *instance;
|
||||||
char *str="";
|
cxobj *xtop = NULL;
|
||||||
cg_var *cv;
|
cxobj *xrpc;
|
||||||
cxobj *xret=NULL;
|
cxobj *xret = NULL;
|
||||||
cxobj *xerr;
|
|
||||||
cxobj *xdata;
|
|
||||||
|
|
||||||
if (cvec_len(vars)==2){
|
/* User supplied variable in CLI command */
|
||||||
if ((cv = cvec_i(vars, 1)) != NULL)
|
instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */
|
||||||
str = cv_string_get(cv);
|
/* Create XML for fib-route netconf RPC */
|
||||||
}
|
if (clicon_xml_parse(&xtop, "<rpc><fib-route><routing-instance-name>%s</routing-instance-name></fib-route></rpc>", instance) < 0)
|
||||||
if ((msg = clicon_msg_encode("<rpc><myrouting>%s</myrouting></rpc>", str)) == NULL)
|
|
||||||
goto done;
|
goto done;
|
||||||
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
|
/* Skip top-level */
|
||||||
|
xrpc = xml_child_i(xtop, 0);
|
||||||
|
/* Send to backend */
|
||||||
|
if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
/* Print result */
|
||||||
clicon_rpc_generate_error(xerr);
|
xml_print(stdout, xml_child_i(xret, 0));
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((xdata = xpath_first(xret, "//ok")) != NULL)
|
|
||||||
cli_output(stdout, "%s\n", xml_body(xdata));
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (xret)
|
if (xret)
|
||||||
xml_free(xret);
|
xml_free(xret);
|
||||||
if (msg)
|
if (xtop)
|
||||||
free(msg);
|
xml_free(xtop);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ CLICON_PROMPT="%U@%H> ";
|
||||||
CLICON_PLUGIN="routing_cli";
|
CLICON_PLUGIN="routing_cli";
|
||||||
|
|
||||||
# Note, when switching to PT, change datamodel to only @datamodel
|
# Note, when switching to PT, change datamodel to only @datamodel
|
||||||
set @datamodel:ietf-ip, cli_set();
|
set @datamodel:example, cli_set();
|
||||||
merge @datamodel:ietf-ip, cli_merge();
|
merge @datamodel:example, cli_merge();
|
||||||
create @datamodel:ietf-ip, cli_create();
|
create @datamodel:example, cli_create();
|
||||||
delete("Delete a configuration item") @datamodel:ietf-ip, cli_del();
|
delete("Delete a configuration item") @datamodel:example, cli_del();
|
||||||
|
|
||||||
validate("Validate changes"), cli_validate();
|
validate("Validate changes"), cli_validate();
|
||||||
commit("Commit the changes"), cli_commit();
|
commit("Commit the changes"), cli_commit();
|
||||||
|
|
@ -49,7 +49,7 @@ load("Load configuration from XML file") <filename:string>("Filename (local file
|
||||||
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
|
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
|
||||||
}
|
}
|
||||||
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
|
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
|
||||||
downcall("This is a downcall") <str:rest>, downcall();
|
rpc("fib-route rpc") <instance:string>("routing instance"), fib_route_rpc("myarg");
|
||||||
notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text");
|
notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text");
|
||||||
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
|
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
|
||||||
lock,cli_lock("candidate");
|
lock,cli_lock("candidate");
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ enum {
|
||||||
*/
|
*/
|
||||||
int xml2txt(FILE *f, cxobj *x, int level);
|
int xml2txt(FILE *f, cxobj *x, int level);
|
||||||
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
|
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
|
||||||
int xml_yang_validate(cxobj *xt, yang_stmt *ys) ;
|
int xml_yang_validate_add(cxobj *xt, void *arg);
|
||||||
|
int xml_yang_validate_all(cxobj *xt, void *arg);
|
||||||
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
|
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
|
||||||
int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0);
|
int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0);
|
||||||
int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
|
int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
* - Cant use the symbols in this file because yacc needs token definitions
|
* - Cant use the symbols in this file because yacc needs token definitions
|
||||||
*/
|
*/
|
||||||
enum rfc_6020{
|
enum rfc_6020{
|
||||||
Y_ANYXML,
|
Y_ANYXML = 0,
|
||||||
Y_ARGUMENT,
|
Y_ARGUMENT,
|
||||||
Y_AUGMENT,
|
Y_AUGMENT,
|
||||||
Y_BASE,
|
Y_BASE,
|
||||||
|
|
@ -204,11 +204,12 @@ yang_stmt *yang_find(yang_node *yn, int keyword, char *argument);
|
||||||
yang_stmt *yang_find_syntax(yang_node *yn, char *argument);
|
yang_stmt *yang_find_syntax(yang_node *yn, char *argument);
|
||||||
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name);
|
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name);
|
||||||
|
|
||||||
|
int yang_print(FILE *f, yang_node *yn);
|
||||||
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
||||||
int yang_print(FILE *f, yang_node *yn, int marginal);
|
|
||||||
int yang_parse(clicon_handle h, const char *yang_dir,
|
int yang_parse(clicon_handle h, const char *yang_dir,
|
||||||
const char *module, const char *revision, yang_spec *ysp);
|
const char *module, const char *revision, yang_spec *ysp);
|
||||||
int yang_apply(yang_node *yn, yang_applyfn_t fn, void *arg);
|
int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn,
|
||||||
|
void *arg);
|
||||||
yang_node *yang_xpath_abs(yang_node *yn, char *xpath);
|
yang_node *yang_xpath_abs(yang_node *yn, char *xpath);
|
||||||
yang_node *yang_xpath(yang_node *yn, char *xpath);
|
yang_node *yang_xpath(yang_node *yn, char *xpath);
|
||||||
cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);
|
cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);
|
||||||
|
|
|
||||||
|
|
@ -272,8 +272,8 @@ clicon_rpc_get_config(clicon_handle h,
|
||||||
/* Send xml error back: first check error, then ok */
|
/* Send xml error back: first check error, then ok */
|
||||||
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
|
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
|
||||||
xd = xml_parent(xd); /* point to rpc-reply */
|
xd = xml_parent(xd); /* point to rpc-reply */
|
||||||
else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL)
|
else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL)
|
||||||
if ((xd = xml_new("config", NULL)) == NULL)
|
if ((xd = xml_new("data", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (xt){
|
if (xt){
|
||||||
if (xml_rm(xd) < 0)
|
if (xml_rm(xd) < 0)
|
||||||
|
|
@ -521,8 +521,8 @@ clicon_rpc_get(clicon_handle h,
|
||||||
/* Send xml error back: first check error, then ok */
|
/* Send xml error back: first check error, then ok */
|
||||||
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
|
if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL)
|
||||||
xd = xml_parent(xd); /* point to rpc-reply */
|
xd = xml_parent(xd); /* point to rpc-reply */
|
||||||
else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL)
|
else if ((xd = xpath_first(xret, "/rpc-reply/data")) == NULL)
|
||||||
if ((xd = xml_new("config", NULL)) == NULL)
|
if ((xd = xml_new("data", NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
if (xt){
|
if (xt){
|
||||||
if (xml_rm(xd) < 0)
|
if (xml_rm(xd) < 0)
|
||||||
|
|
|
||||||
|
|
@ -274,16 +274,62 @@ xml2cli(FILE *f,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Validate an xml node of type leafref, ensure the value is one of that path's reference
|
||||||
|
* @param[in] xt XML leaf node of type leafref
|
||||||
|
* @param[in] ytype Yang type statement belonging to the XML node
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
validate_leafref(cxobj *xt,
|
||||||
|
yang_stmt *ytype)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
yang_stmt *ypath;
|
||||||
|
cxobj **xvec = NULL;
|
||||||
|
cxobj *x;
|
||||||
|
int i;
|
||||||
|
size_t xlen = 0;
|
||||||
|
char *leafrefbody;
|
||||||
|
char *leafbody;
|
||||||
|
|
||||||
/*! Validate a single XML node with yang specification
|
|
||||||
* - If no value and mandatory flag set in spec, report error.
|
if ((leafrefbody = xml_body(xt)) == NULL)
|
||||||
* - Validate value versus spec, and report error if no match. Currently
|
return 0;
|
||||||
* only int ranges and string regexp checked.
|
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||||
* @retval 0 OK
|
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
|
||||||
*/
|
goto done;
|
||||||
|
}
|
||||||
|
if (xpath_vec(xt, ypath->ys_argument, &xvec, &xlen) < 0)
|
||||||
|
goto done;
|
||||||
|
for (i = 0; i < xlen; i++) {
|
||||||
|
x = xvec[i];
|
||||||
|
if ((leafbody = xml_body(x)) == NULL)
|
||||||
|
continue;
|
||||||
|
if (strcmp(leafbody, leafrefbody) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i==xlen){
|
||||||
|
clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s",
|
||||||
|
leafrefbody);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (xvec)
|
||||||
|
free(xvec);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Validate a single XML node with yang specification for added entry
|
||||||
|
* 1. Check if mandatory leafs present as subs.
|
||||||
|
* 2. Check leaf values, eg int ranges and string regexps.
|
||||||
|
* @param[in] xt XML node to be validated
|
||||||
|
* @retval 0 Valid OK
|
||||||
|
* @retval -1 Validation failed
|
||||||
|
* @see xml_yang_validate_all
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
xml_yang_validate(cxobj *xt,
|
xml_yang_validate_add(cxobj *xt,
|
||||||
yang_stmt *ys0)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cg_var *cv = NULL;
|
cg_var *cv = NULL;
|
||||||
|
|
@ -294,7 +340,7 @@ xml_yang_validate(cxobj *xt,
|
||||||
char *body;
|
char *body;
|
||||||
|
|
||||||
/* if not given by argument (overide) use default link */
|
/* if not given by argument (overide) use default link */
|
||||||
ys = ys0?ys0:xml_spec(xt);
|
ys = xml_spec(xt);
|
||||||
switch (ys->ys_keyword){
|
switch (ys->ys_keyword){
|
||||||
case Y_LIST:
|
case Y_LIST:
|
||||||
/* fall thru */
|
/* fall thru */
|
||||||
|
|
@ -346,6 +392,43 @@ xml_yang_validate(cxobj *xt,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Validate a single XML node with yang specification for all (not only added) entries
|
||||||
|
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
|
||||||
|
* @param[in] xt XML node to be validated
|
||||||
|
* @retval 0 Valid OK
|
||||||
|
* @retval -1 Validation failed
|
||||||
|
* @see xml_yang_validate_add
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
xml_yang_validate_all(cxobj *xt,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
yang_stmt *ys;
|
||||||
|
yang_stmt *ytype;
|
||||||
|
|
||||||
|
/* if not given by argument (overide) use default link */
|
||||||
|
ys = xml_spec(xt);
|
||||||
|
switch (ys->ys_keyword){
|
||||||
|
case Y_LEAF:
|
||||||
|
/* fall thru */
|
||||||
|
case Y_LEAF_LIST:
|
||||||
|
/* Special case if leaf is leafref, then first check against
|
||||||
|
current xml tree
|
||||||
|
*/
|
||||||
|
if ((ytype = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL &&
|
||||||
|
strcmp(ytype->ys_argument, "leafref") == 0)
|
||||||
|
if (validate_leafref(xt, ytype) < 0)
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Translate a single xml node to a cligen variable vector. Note not recursive
|
/*! Translate a single xml node to a cligen variable vector. Note not recursive
|
||||||
* @param[in] xt XML tree containing one top node
|
* @param[in] xt XML tree containing one top node
|
||||||
* @param[in] ys Yang spec containing type specification of top-node of xt
|
* @param[in] ys Yang spec containing type specification of top-node of xt
|
||||||
|
|
@ -1908,3 +1991,4 @@ xml_merge(cxobj *x0,
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,7 @@ recursive_find(cxobj *xn,
|
||||||
* - @<attr>=<value>
|
* - @<attr>=<value>
|
||||||
* - <number>
|
* - <number>
|
||||||
* - <name>=<value> # RelationalExpr '=' RelationalExpr
|
* - <name>=<value> # RelationalExpr '=' RelationalExpr
|
||||||
|
* - <name>=current()<xpath> XXX
|
||||||
* @see https://www.w3.org/TR/xpath/#predicates
|
* @see https://www.w3.org/TR/xpath/#predicates
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
|
|
@ -565,6 +566,7 @@ xpath_find(struct xpath_element *xe,
|
||||||
cxobj *xv;
|
cxobj *xv;
|
||||||
int descendants = 0;
|
int descendants = 0;
|
||||||
cxobj **vec1 = NULL;
|
cxobj **vec1 = NULL;
|
||||||
|
cxobj *xparent;
|
||||||
size_t vec1len = 0;
|
size_t vec1len = 0;
|
||||||
struct xpath_predicate *xp;
|
struct xpath_predicate *xp;
|
||||||
|
|
||||||
|
|
@ -587,10 +589,13 @@ xpath_find(struct xpath_element *xe,
|
||||||
case A_SELF:
|
case A_SELF:
|
||||||
break;
|
break;
|
||||||
case A_PARENT:
|
case A_PARENT:
|
||||||
|
j = 0;
|
||||||
for (i=0; i<vec0len; i++){
|
for (i=0; i<vec0len; i++){
|
||||||
xv = vec0[i];
|
xv = vec0[i];
|
||||||
vec0[i] = xml_parent(xv);
|
if ((xparent = xml_parent(xv)) != NULL)
|
||||||
|
vec0[j++] = xparent;
|
||||||
}
|
}
|
||||||
|
vec0len = j;
|
||||||
break;
|
break;
|
||||||
case A_ROOT: /* set list to NULL */
|
case A_ROOT: /* set list to NULL */
|
||||||
x = vec0[0];
|
x = vec0[0];
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ yn_each(yang_node *yn,
|
||||||
*
|
*
|
||||||
* @param[in] yn Yang node, current context node.
|
* @param[in] yn Yang node, current context node.
|
||||||
* @param[in] keyword if 0 match any keyword
|
* @param[in] keyword if 0 match any keyword
|
||||||
* @param[in] argument if NULL, match any argument.
|
* @param[in] argument String compare w wrgument. if NULL, match any.
|
||||||
* This however means that if you actually want to match only a yang-stmt with
|
* This however means that if you actually want to match only a yang-stmt with
|
||||||
* argument==NULL you cannot, but I have not seen any such examples.
|
* argument==NULL you cannot, but I have not seen any such examples.
|
||||||
* @see yang_find_syntax
|
* @see yang_find_syntax
|
||||||
|
|
@ -682,43 +682,41 @@ quotedstring(char *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Print yang specification to file
|
/*! Print yang specification to file
|
||||||
|
* @param[in] f File to print to.
|
||||||
|
* @param[in] yn Yang node to print
|
||||||
* @see yang_print_cbuf
|
* @see yang_print_cbuf
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
yang_print(FILE *f,
|
yang_print(FILE *f,
|
||||||
yang_node *yn,
|
yang_node *yn)
|
||||||
int marginal)
|
|
||||||
{
|
{
|
||||||
yang_stmt *ys = NULL;
|
int retval = -1;
|
||||||
|
cbuf *cb = NULL;
|
||||||
|
|
||||||
while ((ys = yn_each(yn, ys)) != NULL) {
|
if ((cb = cbuf_new()) == NULL){
|
||||||
fprintf(f, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword));
|
clicon_err(OE_YANG, errno, "%s: cbuf_new", __FUNCTION__);
|
||||||
fflush(f);
|
goto done;
|
||||||
if (ys->ys_argument){
|
|
||||||
if (quotedstring(ys->ys_argument))
|
|
||||||
fprintf(f, " \"%s\"", ys->ys_argument);
|
|
||||||
else
|
|
||||||
fprintf(f, " %s", ys->ys_argument);
|
|
||||||
}
|
|
||||||
if (ys->ys_len){
|
|
||||||
fprintf(f, " {\n");
|
|
||||||
yang_print(f, (yang_node*)ys, marginal+3);
|
|
||||||
fprintf(f, "%*s%s\n", marginal, "", "}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
fprintf(f, ";\n");
|
|
||||||
}
|
}
|
||||||
return 0;
|
if (yang_print_cbuf(cb, yn, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
fprintf(f, "%s", cbuf_get(cb));
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Print yang specification to cligen buf
|
/*! Print yang specification to cligen buf
|
||||||
|
* @param[in] cb Cligen buffer. This is where the pretty print is.
|
||||||
|
* @param[in] yn Yang node to print
|
||||||
|
* @param[in] marginal Tab indentation, mainly for recursion.
|
||||||
* @code
|
* @code
|
||||||
* cbuf *cb = cbuf_new();
|
* cbuf *cb = cbuf_new();
|
||||||
* yang_print_cbuf(cb, yn, 0);
|
* yang_print_cbuf(cb, yn, 0);
|
||||||
* // output is in cbuf_buf(cb);
|
* // output is in cbuf_buf(cb);
|
||||||
* cbuf_free(cb);
|
* cbuf_free(cb);
|
||||||
* @endcode
|
* @endcode
|
||||||
* @see yang_print
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
yang_print_cbuf(cbuf *cb,
|
yang_print_cbuf(cbuf *cb,
|
||||||
|
|
@ -1587,17 +1585,18 @@ yang_parse(clicon_handle h,
|
||||||
clicon_dbspec_name_set(h, ymod->ys_argument);
|
clicon_dbspec_name_set(h, ymod->ys_argument);
|
||||||
|
|
||||||
/* Resolve all types */
|
/* Resolve all types */
|
||||||
yang_apply((yang_node*)ysp, ys_resolve_type, NULL);
|
yang_apply((yang_node*)ysp, Y_TYPE, ys_resolve_type, NULL);
|
||||||
|
|
||||||
/* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */
|
/* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */
|
||||||
if (yang_expand((yang_node*)ysp) < 0)
|
if (yang_expand((yang_node*)ysp) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
yang_apply((yang_node*)ymod, ys_flag_reset, (void*)YANG_FLAG_MARK);
|
yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
|
||||||
/* Step 4: Go through parse tree and populate it with cv types */
|
|
||||||
if (yang_apply((yang_node*)ysp, ys_populate, NULL) < 0)
|
/* Step 3: Go through parse tree and populate it with cv types */
|
||||||
|
if (yang_apply((yang_node*)ysp, -1, ys_populate, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Step 3: Top-level augmentation of all modules */
|
/* Step 4: Top-level augmentation of all modules */
|
||||||
if (yang_augment_spec(ysp) < 0)
|
if (yang_augment_spec(ysp) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
|
@ -1606,7 +1605,6 @@ yang_parse(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Apply a function call recursively on all yang-stmt s recursively
|
/*! Apply a function call recursively on all yang-stmt s recursively
|
||||||
*
|
*
|
||||||
* Recursively traverse all yang-nodes in a parse-tree and apply fn(arg) for
|
* Recursively traverse all yang-nodes in a parse-tree and apply fn(arg) for
|
||||||
|
|
@ -1614,34 +1612,49 @@ yang_parse(clicon_handle h,
|
||||||
* argument as args.
|
* argument as args.
|
||||||
* The tree is traversed depth-first, which at least guarantees that a parent is
|
* The tree is traversed depth-first, which at least guarantees that a parent is
|
||||||
* traversed before a child.
|
* traversed before a child.
|
||||||
* @param[in] xn XML node
|
* @param[in] yn yang node
|
||||||
* @param[in] type matching type or -1 for any
|
* @param[in] key yang keyword to use as filer or -1 for all
|
||||||
* @param[in] fn Callback
|
* @param[in] fn Callback
|
||||||
* @param[in] arg Argument
|
* @param[in] arg Argument
|
||||||
|
* @retval -1 Error, aborted at first error encounter
|
||||||
|
* @retval 0 OK, all nodes traversed
|
||||||
|
* @retval n OK, aborted at first encounter of first match
|
||||||
* @code
|
* @code
|
||||||
* int ys_fn(yang_stmt *ys, void *arg)
|
* int ys_fn(yang_stmt *ys, void *arg)
|
||||||
* {
|
* {
|
||||||
* return 0;
|
* return 0;
|
||||||
* }
|
* }
|
||||||
* yang_apply((yang_node*)ys, ys_fn, NULL);
|
* yang_apply((yang_node*)ys, Y_TYPE, ys_fn, NULL);
|
||||||
* @endcode
|
* @endcode
|
||||||
* @note do not delete or move around any children during this function
|
* @note do not delete or move around any children during this function
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
yang_apply(yang_node *yn,
|
yang_apply(yang_node *yn,
|
||||||
|
enum rfc_6020 keyword,
|
||||||
yang_applyfn_t fn,
|
yang_applyfn_t fn,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
yang_stmt *ys = NULL;
|
yang_stmt *ys = NULL;
|
||||||
int i;
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
for (i=0; i<yn->yn_len; i++){
|
for (i=0; i<yn->yn_len; i++){
|
||||||
ys = yn->yn_stmt[i];
|
ys = yn->yn_stmt[i];
|
||||||
if (fn(ys, arg) < 0)
|
if (keyword == -1 || keyword == ys->ys_keyword){
|
||||||
|
if ((ret = fn(ys, arg)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret > 0){
|
||||||
|
retval = ret;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((ret = yang_apply((yang_node*)ys, keyword, fn, arg)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (yang_apply((yang_node*)ys, fn, arg) < 0)
|
if (ret > 0){
|
||||||
|
retval = ret;
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -1929,7 +1942,7 @@ yang_spec_main(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
clicon_dbspec_yang_set(h, yspec);
|
clicon_dbspec_yang_set(h, yspec);
|
||||||
if (printspec)
|
if (printspec)
|
||||||
yang_print(f, (yang_node*)yspec, 0);
|
yang_print(f, (yang_node*)yspec);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ static const map_str2int ytmap[] = {
|
||||||
{"int16", CGV_INT16},
|
{"int16", CGV_INT16},
|
||||||
{"int64", CGV_INT64},
|
{"int64", CGV_INT64},
|
||||||
{"leafref", CGV_STRING}, /* XXX */
|
{"leafref", CGV_STRING}, /* XXX */
|
||||||
|
|
||||||
{"uint8", CGV_UINT8},
|
{"uint8", CGV_UINT8},
|
||||||
{"uint16", CGV_UINT16},
|
{"uint16", CGV_UINT16},
|
||||||
{"uint32", CGV_UINT32},
|
{"uint32", CGV_UINT32},
|
||||||
|
|
@ -105,12 +104,6 @@ yang_builtin(char *type)
|
||||||
{
|
{
|
||||||
if (clicon_str2int(ytmap, type) != -1)
|
if (clicon_str2int(ytmap, type) != -1)
|
||||||
return 1;
|
return 1;
|
||||||
#if 0
|
|
||||||
const struct map_str2int *yt;
|
|
||||||
for (yt = &ytmap[0]; yt->ms_str; yt++)
|
|
||||||
if (strcmp(yt->ms_str, type) == 0)
|
|
||||||
return 1;
|
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,8 +218,6 @@ ys_resolve_type(yang_stmt *ys,
|
||||||
uint8_t fraction = 0;
|
uint8_t fraction = 0;
|
||||||
yang_stmt *resolved = NULL;
|
yang_stmt *resolved = NULL;
|
||||||
|
|
||||||
if (ys->ys_keyword != Y_TYPE)
|
|
||||||
return 0;
|
|
||||||
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
|
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
|
||||||
&options, &mincv, &maxcv, &pattern, &fraction) < 0)
|
&options, &mincv, &maxcv, &pattern, &fraction) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -857,6 +848,7 @@ yang_type_resolve(yang_stmt *ys,
|
||||||
ylength = yang_find((yang_node*)ytype, Y_LENGTH, NULL);
|
ylength = yang_find((yang_node*)ytype, Y_LENGTH, NULL);
|
||||||
ypattern = yang_find((yang_node*)ytype, Y_PATTERN, NULL);
|
ypattern = yang_find((yang_node*)ytype, Y_PATTERN, NULL);
|
||||||
yfraction = yang_find((yang_node*)ytype, Y_FRACTION_DIGITS, NULL);
|
yfraction = yang_find((yang_node*)ytype, Y_FRACTION_DIGITS, NULL);
|
||||||
|
|
||||||
/* Check if type is basic type. If so, return that */
|
/* Check if type is basic type. If so, return that */
|
||||||
if (prefix == NULL && yang_builtin(type)){
|
if (prefix == NULL && yang_builtin(type)){
|
||||||
*yrestype = ytype;
|
*yrestype = ytype;
|
||||||
|
|
@ -867,8 +859,10 @@ yang_type_resolve(yang_stmt *ys,
|
||||||
|
|
||||||
/* Not basic type. Now check if prefix which means we look in other module */
|
/* Not basic type. Now check if prefix which means we look in other module */
|
||||||
if (prefix){ /* Go to top and find import that matches */
|
if (prefix){ /* Go to top and find import that matches */
|
||||||
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL)
|
if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){
|
||||||
|
clicon_err(OE_DB, 0, "Module not resolved: %s", prefix);
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL)
|
if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL)
|
||||||
goto ok; /* unresolved */
|
goto ok; /* unresolved */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ This directory contains testing code for clixon and the example
|
||||||
routing application:
|
routing application:
|
||||||
- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script
|
- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script
|
||||||
- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically.
|
- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically.
|
||||||
- test1.sh CLI tests
|
- test_cli.sh CLI tests
|
||||||
- test2.sh Netconf tests
|
- test_netconf.sh Netconf tests
|
||||||
- test3.sh Restconf tests
|
- test_restconf.sh Restconf tests
|
||||||
- test4.sh Yang tests
|
- test_yang.sh Yang tests for constructs not in the example.
|
||||||
- test5.sh Datastore tests
|
- test_leafref.sh Yang leafref tests
|
||||||
|
- test_datastore.sh Datastore tests
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Test6: Yang specifics: rpc and state info
|
|
||||||
|
|
||||||
# include err() and new() functions
|
|
||||||
. ./lib.sh
|
|
||||||
|
|
||||||
# For memcheck
|
|
||||||
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
|
|
||||||
clixon_netconf=clixon_netconf
|
|
||||||
clixon_cli=clixon_cli
|
|
||||||
|
|
||||||
cat <<EOF > /tmp/rpc.yang
|
|
||||||
module ietf-ip{
|
|
||||||
rpc fib-route {
|
|
||||||
input {
|
|
||||||
leaf name {
|
|
||||||
type string;
|
|
||||||
mandatory "true";
|
|
||||||
}
|
|
||||||
leaf destination-address {
|
|
||||||
type string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output {
|
|
||||||
container route {
|
|
||||||
leaf address{
|
|
||||||
type string;
|
|
||||||
}
|
|
||||||
leaf address{
|
|
||||||
type string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# kill old backend (if any)
|
|
||||||
new "kill old backend"
|
|
||||||
sudo clixon_backend -zf $clixon_cf -y /tmp/rpc
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
err
|
|
||||||
fi
|
|
||||||
|
|
||||||
new "start backend"
|
|
||||||
# start new backend
|
|
||||||
sudo clixon_backend -If $clixon_cf -y /tmp/rpc
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
err
|
|
||||||
fi
|
|
||||||
new "netconf rpc (notyet)"
|
|
||||||
#expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/rpc" "<rpc><fib-route><name></name></fib-route></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
|
||||||
|
|
||||||
new "Kill backend"
|
|
||||||
# Check if still alive
|
|
||||||
pid=`pgrep clixon_backend`
|
|
||||||
if [ -z "$pid" ]; then
|
|
||||||
err "backend already dead"
|
|
||||||
fi
|
|
||||||
# kill backend
|
|
||||||
sudo clixon_backend -zf $clixon_cf
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
err "kill backend"
|
|
||||||
fi
|
|
||||||
|
|
@ -31,8 +31,8 @@ new "cli tests"
|
||||||
new "cli configure top"
|
new "cli configure top"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf set interfaces" ""
|
expectfn "$clixon_cli -1f $clixon_cf set interfaces" ""
|
||||||
|
|
||||||
new "cli show configuration top"
|
new "cli show configuration top (no presence)"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces$"
|
expectfn "$clixon_cli -1f $clixon_cf show conf cli" ""
|
||||||
|
|
||||||
new "cli configure delete top"
|
new "cli configure delete top"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf delete interfaces" ""
|
expectfn "$clixon_cli -1f $clixon_cf delete interfaces" ""
|
||||||
|
|
@ -87,7 +87,7 @@ expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" ""
|
||||||
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" ""
|
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" ""
|
||||||
|
|
||||||
new "cli downcall"
|
new "cli downcall"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf -l o downcall \"This is a test =====\"" "^\"This is a test =====\"$"
|
expectfn "$clixon_cli -1f $clixon_cf -l o rpc ipv4" "^<rpc-reply>"
|
||||||
|
|
||||||
new "Kill backend"
|
new "Kill backend"
|
||||||
# Check if still alive
|
# Check if still alive
|
||||||
104
test/test_leafref.sh
Executable file
104
test/test_leafref.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Test7: Yang specifics: leafref
|
||||||
|
|
||||||
|
# include err() and new() functions
|
||||||
|
. ./lib.sh
|
||||||
|
|
||||||
|
# For memcheck
|
||||||
|
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
|
||||||
|
clixon_netconf=clixon_netconf
|
||||||
|
clixon_cli=clixon_cli
|
||||||
|
|
||||||
|
cat <<EOF > /tmp/leafref.yang
|
||||||
|
module example{
|
||||||
|
typedef admin-status{
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
list interface {
|
||||||
|
key "name";
|
||||||
|
leaf name {
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
leaf admin-status {
|
||||||
|
type admin-status;
|
||||||
|
}
|
||||||
|
list address {
|
||||||
|
key "ip";
|
||||||
|
leaf ip {
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container default-address {
|
||||||
|
leaf ifname {
|
||||||
|
type leafref {
|
||||||
|
path "../../interface/name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaf address {
|
||||||
|
type leafref {
|
||||||
|
path "../../interface[name=eth0]"
|
||||||
|
+ "/address/ip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
# path "../../interface[name = current()/../ifname]"
|
||||||
|
|
||||||
|
# kill old backend (if any)
|
||||||
|
new "kill old backend"
|
||||||
|
sudo clixon_backend -zf $clixon_cf -y /tmp/leafref
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "start backend"
|
||||||
|
# start new backend
|
||||||
|
sudo clixon_backend -If $clixon_cf -y /tmp/leafref
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "leafref base config"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><edit-config><target><candidate/></target><config><interface><name>eth0</name><admin-status>up</admin-status><address><ip>192.0.2.1</ip></address><address><ip>192.0.2.2</ip></address></interface><interface><name>lo</name><admin-status>up</admin-status><address><ip>127.0.0.1</ip></address></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "leafref get config"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interface><name>eth0</name>'
|
||||||
|
|
||||||
|
new "leafref base commit"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "leafref add wrong ref"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><edit-config><target><candidate/></target><config><default-address><ifname>eth3</ifname><address>10.0.4.6</address></default-address></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "leafref validate"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>missing-attribute</error-tag>"
|
||||||
|
|
||||||
|
new "leafref discard-changes"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "leafref add correct ref"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><edit-config><target><candidate/></target><config><default-address><ifname>eth0</ifname><address>192.0.2.2</address></default-address></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "leafref validate (ok)"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>"
|
||||||
|
|
||||||
|
new "leafref delete leaf"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><edit-config><target><candidate/></target><config><interface operation=\"delete\"><name>eth0</name></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>"
|
||||||
|
|
||||||
|
new "leafref validate (should fail)"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/leafref" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>missing-attribute</error-tag>"
|
||||||
|
|
||||||
|
|
||||||
|
new "Kill backend"
|
||||||
|
# Check if still alive
|
||||||
|
pid=`pgrep clixon_backend`
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
err "backend already dead"
|
||||||
|
fi
|
||||||
|
# kill backend
|
||||||
|
sudo clixon_backend -zf $clixon_cf
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err "kill backend"
|
||||||
|
fi
|
||||||
|
|
@ -36,7 +36,7 @@ new "Add subtree eth/0/0 using none and create which should add eth/0/0"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "Check eth/0/0 added using xpath"
|
new "Check eth/0/0 added using xpath"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth/0/0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth/0/0</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth/0/0]"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><type>eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "Re-create same eth/0/0 which should generate error"
|
new "Re-create same eth/0/0 which should generate error"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
|
||||||
|
|
@ -44,8 +44,8 @@ expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate
|
||||||
new "Delete eth/0/0 using none config"
|
new "Delete eth/0/0 using none config"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
#new "Check deleted eth/0/0"
|
new "Check deleted eth/0/0 (non-presence container)"
|
||||||
#expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data><config><interfaces/></config></data></rpc-reply>]]>]]>$'
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc message-id="101"><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply message-id="101"><data/></rpc-reply>]]>]]>$'
|
||||||
|
|
||||||
new "Re-Delete eth/0/0 using none should generate error"
|
new "Re-Delete eth/0/0 using none should generate error"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth/0/0</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
|
||||||
|
|
@ -54,10 +54,10 @@ new "netconf edit config"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth/0/0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get config xpath"
|
new "netconf get config xpath"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled"/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth1</name><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get config xpath parent"
|
new "netconf get config xpath parent"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><config><interfaces><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface[name=eth1]/enabled/../.."/></get-config></rpc>]]>]]>' "^<rpc-reply><data><interfaces><interface><name>eth/0/0</name><enabled>true</enabled></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><enabled>true</enabled><forwarding>false</forwarding><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf validate missing type"
|
new "netconf validate missing type"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
|
||||||
|
|
@ -81,7 +81,7 @@ new "netconf edit config replace"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth2</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth2</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get replaced config"
|
new "netconf get replaced config"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf discard-changes"
|
new "netconf discard-changes"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
@ -111,7 +111,7 @@ new "copy startup"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><copy-config><target><startup/></target><source><candidate/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><copy-config><target><startup/></target><source><candidate/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get startup"
|
new "netconf get startup"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf delete startup"
|
new "netconf delete startup"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
@ -119,6 +119,9 @@ expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><delete-config><target><startup
|
||||||
new "netconf check empty startup"
|
new "netconf check empty startup"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "netconf rpc"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
|
||||||
|
|
||||||
new "netconf subscription"
|
new "netconf subscription"
|
||||||
expectwait "$clixon_netconf -qf $clixon_cf" "<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><notification><event>Routing notification</event></notification>]]>]]>$" 30
|
expectwait "$clixon_netconf -qf $clixon_cf" "<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><notification><event>Routing notification</event></notification>]]>]]>$" 30
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ clixon_netconf=clixon_netconf
|
||||||
clixon_cli=clixon_cli
|
clixon_cli=clixon_cli
|
||||||
|
|
||||||
cat <<EOF > /tmp/test.yang
|
cat <<EOF > /tmp/test.yang
|
||||||
module ietf-ip{
|
module example{
|
||||||
container x {
|
container x {
|
||||||
list y {
|
list y {
|
||||||
key "a b";
|
key "a b";
|
||||||
|
|
@ -35,7 +35,15 @@ module ietf-ip{
|
||||||
leaf g {
|
leaf g {
|
||||||
type string;
|
type string;
|
||||||
}
|
}
|
||||||
container h {
|
container nopresence {
|
||||||
|
description "No presence should be removed if no children";
|
||||||
|
leaf j {
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container presence {
|
||||||
|
description "Presence should not be removed even if no children";
|
||||||
|
presence "even if empty should remain";
|
||||||
leaf j {
|
leaf j {
|
||||||
type string;
|
type string;
|
||||||
}
|
}
|
||||||
|
|
@ -70,29 +78,33 @@ new "netconf commit"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get config xpath"
|
new "netconf get config xpath"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=1][b=2]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><y><a>1</a><b>2</b><c>5</c></y></x></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=1][b=2]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><y><a>1</a><b>2</b><c>5</c></y></x></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf edit leaf-list"
|
new "netconf edit leaf-list"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><x><f><e>hej</e><e>hopp</e></f></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><x><f><e>hej</e><e>hopp</e></f></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get leaf-list"
|
new "netconf get leaf-list"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f/e\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><f><e>hej</e><e>hopp</e></f></x></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f/e\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><f><e>hej</e><e>hopp</e></f></x></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get leaf-list path"
|
new "netconf get leaf-list path"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e=hej]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><f><e>hej</e><e>hopp</e></f></x></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/f[e=hej]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><f><e>hej</e><e>hopp</e></f></x></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "netconf get (state data XXX should be some)"
|
new "netconf get (should be some)"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply><data><config><x><y><a>1</a><b>2</b><c>5</c></y><d/></x></config></data></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get><filter type=\"xpath\" select=\"/\"/></get></rpc>]]>]]>" "^<rpc-reply><data><x><y><a>1</a><b>2</b><c>5</c></y><d/></x></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "cli set leaf-list"
|
new "cli set leaf-list"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test set x f e foo" ""
|
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test set x f e foo" ""
|
||||||
|
|
||||||
new "cli show leaf-list"
|
new "cli show leaf-list"
|
||||||
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test show xpath /x/f/e" "<e>foo</e>"
|
expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test show xpath /x/f/e" "<e>foo</e>"
|
||||||
|
|
||||||
new "netconf set state data (not allowed)"
|
new "netconf set state data (not allowed)"
|
||||||
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><state><op>42</op></state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value"
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><state><op>42</op></state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value"
|
||||||
|
|
||||||
|
new "netconf set presence and not present"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><x><nopresence/><presence/></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
new "netconf get"
|
||||||
|
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/*presence\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><presence/></x></data></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
new "Kill backend"
|
new "Kill backend"
|
||||||
# Check if still alive
|
# Check if still alive
|
||||||
Loading…
Add table
Add a link
Reference in a new issue