* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file.

* For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily.
* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK.
  * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath`
* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`.
This commit is contained in:
Olof hagsand 2019-01-02 15:18:29 +01:00
parent 861300d6c0
commit 0baebc93fd
71 changed files with 2679 additions and 1573 deletions

View file

@ -1,19 +1,33 @@
# Clixon example
* [Content](#content)
* [Compile and run](#compile)
* [Using the CLI](#using-the-cli)
* [Using netconf](#using-netconf)
* [Streams](#streams)
* [RPC Operations](#rpc-operations)
* [State data](#state-data)
* [Authentication and NACM](#authentication-and-nacm)
* [Systemd](#systemd)
* [Docker](#docker)
* [Plugins](#plugins)
## Content
This directory contains a Clixon example which includes a simple example. It contains the following files:
* example.xml The configuration file. See yang/clixon-config@<date>.yang for all available fields.
* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules.
* example_cli.cli CLIgen specification.
* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`).
* example_backend.c Backend callback plugin including example of:
* `example.xml` The configuration file. See (yang/clixon-config@<date>.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields.
* `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules.
* `example_cli.cli` CLIgen specification.
* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`).
* `example_backend.c` Backend callback plugin including example of:
* transaction callbacks (validate/commit),
* notification,
* rpc handler
* state-data handler, ie non-config data
* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically.
* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback
* example_netconf.c Netconf callback plugin
* Makefile.in Example makefile where plugins are built and installed
* `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically.
* `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback
* `example_netconf.c` Netconf callback plugin
* `Makefile.in` Example makefile where plugins are built and installed
## Compile and run
@ -47,10 +61,37 @@ Send restconf command
curl -G http://127.0.0.1/restconf/data
```
## Setting data example using netconf
## Using the CLI
The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code.
There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details.
The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it.
```
clixon_cli -f /usr/local/etc/example.xml
cli> set interfaces interface eth9 ?
description enabled ipv4
ipv6 link-up-down-trap-enable type
cli> set interfaces interface eth9 type ex:eth
cli> validate
cli> commit
cli> show configuration xml
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth9</name>
<type>ex:eth</type>
<enabled>true</enabled>
</interface>
</interfaces>
cli> delete interfaces interface eth9
```
## Using Netconf
The following example shows how to set data using netconf:
```
<rpc><edit-config><target><candidate/></target><config>
<interfaces>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth1</name>
<enabled>true</enabled>
@ -65,13 +106,13 @@ Send restconf command
</config></edit-config></rpc>]]>]]>
```
## Getting data using netconf
### Getting data using netconf
```
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><configuration><interfaces><interface><ipv4/></interface></interfaces></configuration></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface/ipv4"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth9</name><type>ex:eth</type></interface></interfaces></data></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface"/></get-config></rpc>]]>]]>
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
```
@ -80,23 +121,134 @@ Send restconf command
The example has an EXAMPLE stream notification triggering every 5s. To start a notification
stream in the session using netconf, create a subscription:
```
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2019-01-02T10:20:05.929272</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
...
```
This can also be triggered via the CLI:
```
cli> notify
cli> Routing notification
Routing notification
clixon_cli -f /usr/local/etc/example.xml
cli> notify
cli> event-class fault;
reportingEntity {
card Ethernet0;
}
severity major;
...
cli> no notify
cli>
```
Restconf support is also supported, see [../apps/restconf/README.md].
Restconf support is also supported, see (restc)[../apps/restconf/README.md].
## Initializing a plugin
## RPC Operations
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example using CLI:
```
cli> rpc ipv4
rpc-reply {
route {
address-family ipv4;
next-hop {
next-hop-list 2.3.4.5;
}
source-protocol static;
}
}
```
Netconf:
```
<rpc><fib-route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><routing-instance-name>ipv4</routing-instance-name></fib-route></rpc>]]>]]>
<rpc-reply><route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop><source-protocol>static</source-protocol></route></rpc-reply>]]>]]>
```
Restconf:
```
curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}'
```
### Details
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)).
In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data):
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
"</route></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data(not only configuration data).
In YANG state data is specified with `config false;`. In the example,
`state` is state data, see (example.yang)[example.yang]
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
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
state data.
## Authentication and NACM
The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341):
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
* A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`
## Plugins
The example includes a restonf, netconf, CLI and two backend plugins.
Each plugin is initiated with an API struct followed by a plugin init function.
@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h)
return &api; /* Return NULL on error */
}
```
## Operation data
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example:
```
cli> rpc ipv4
<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>
```
In the backend, a callback is registered (fib_route()) which handles the RPC.
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data, in contrast to
config data.
p
In YANG state data is specified with "config false;". In the example, interface-state is state data.
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
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
state data.
## Authentication and NACM
The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341):
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
* A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd files
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`

View file

@ -96,7 +96,7 @@ example_stream_timer(int fd,
int retval = -1;
clicon_handle h = (clicon_handle)arg;
/* XXX Change to actual netconf notifications */
/* XXX Change to actual netconf notifications and namespace */
if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
@ -129,7 +129,7 @@ fib_route(clicon_handle h, /* Clicon handle */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><route>"
cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
@ -147,7 +147,7 @@ route_count(clicon_handle h,
void *arg,
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><number-of-routes>42</number-of-routes></rpc-reply>");
cprintf(cbret, "<rpc-reply><number-of-routes xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">42</number-of-routes></rpc-reply>");
return 0;
}
@ -180,9 +180,17 @@ example_rpc(clicon_handle h, /* Clicon handle */
{
int retval = -1;
cxobj *x = NULL;
char *namespace;
/* get namespace from rpc name, return back in each output parameter */
if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){
clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe));
goto done;
}
cprintf(cbret, "<rpc-reply>");
while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
if (clicon_xml2cbuf(cbret, x, 0, 0) < 0)
goto done;
}
@ -222,7 +230,7 @@ example_statedata(clicon_handle h,
* Note this state needs to be accomanied by yang snippet
* above
*/
if (xml_parse_string("<state>"
if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
"<op>42</op>"
"</state>", NULL, &xstate) < 0)
goto done;
@ -251,17 +259,22 @@ example_reset(clicon_handle h,
{
int retval = -1;
cxobj *xt = NULL;
int ret;
if (xml_parse_string("<config><interfaces><interface>"
if (xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface>"
"<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0)
goto done;
/* Replace parent w fiorst child */
/* Replace parent w first child */
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0)
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "Error when writing to XML database");
goto done;
}
retval = 0;
done:
if (xt != NULL)

View file

@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h,
goto done;
}
/* Print result */
xml_print(stdout, xml_child_i(xret, 0));
xml2txt(stdout, xml_child_i(xret, 0), 1);
retval = 0;
done:
if (xret)

View file

@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
}
copy("Copy and create a new object") {
interface("Copy interface"){
<name:string expand_dbvar("candidate","/interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
<name:string expand_dbvar("candidate","/ietf-interfaces:interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
}
}
discard("Discard edits (rollback 0)"), discard_changes();

View file

@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h,
void *regarg)
{
clicon_debug(1, "%s restconf", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0;
}

View file

@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h,
{
// FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0;
}