Directory change: Moved example to example/main to make room for other examples
This commit is contained in:
parent
60cb87f998
commit
a269e26c0d
19 changed files with 525 additions and 439 deletions
159
example/main/Makefile.in
Normal file
159
example/main/Makefile.in
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
#
|
||||
# This file is part of CLIXON
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# the GNU General Public License Version 3 or later (the "GPL"),
|
||||
# in which case the provisions of the GPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of the GPL, and not to allow others to
|
||||
# use your version of this file under the terms of Apache License version 2,
|
||||
# indicate your decision by deleting the provisions above and replace them with
|
||||
# the notice and other provisions required by the GPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the Apache License version 2 or the GPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
#
|
||||
VPATH = @srcdir@
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
prefix = @prefix@
|
||||
bindir = @bindir@
|
||||
includedir = @includedir@
|
||||
datarootdir = @datarootdir@
|
||||
sysconfdir = @sysconfdir@
|
||||
datarootdir = @datarootdir@
|
||||
localstatedir = @localstatedir@
|
||||
libdir = @exec_prefix@/lib
|
||||
|
||||
APPNAME = example
|
||||
# Here is where example yang appears
|
||||
CLIXON_DATADIR = @CLIXON_DATADIR@
|
||||
# Install here if you want default clixon location:
|
||||
CLIXON_DEFAULT_CONFIG = @CLIXON_DEFAULT_CONFIG@
|
||||
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@ -rdynamic -fPIC
|
||||
INSTALLFLAGS = @INSTALLFLAGS@
|
||||
with_restconf = @with_restconf@
|
||||
|
||||
INCLUDES = -I$(includedir) @INCLUDES@
|
||||
CPPFLAGS = @CPPFLAGS@ -fPIC
|
||||
|
||||
BE_PLUGIN = $(APPNAME)_backend.so
|
||||
BE2_PLUGIN = $(APPNAME)_backend_nacm.so
|
||||
CLI_PLUGIN = $(APPNAME)_cli.so
|
||||
NETCONF_PLUGIN = $(APPNAME)_netconf.so
|
||||
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
|
||||
|
||||
PLUGINS = $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN)
|
||||
ifeq ($(with_restconf),yes)
|
||||
PLUGINS += $(RESTCONF_PLUGIN)
|
||||
endif
|
||||
|
||||
.PHONY: all clean depend install
|
||||
|
||||
all: $(PLUGINS)
|
||||
|
||||
.SUFFIXES: .c .o
|
||||
|
||||
# implicit rule
|
||||
.c.o:
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $<
|
||||
|
||||
CLISPECS = $(APPNAME)_cli.cli
|
||||
|
||||
YANGSPECS = clixon-example@2019-01-13.yang
|
||||
|
||||
# Backend plugin
|
||||
BE_SRC = $(APPNAME)_backend.c
|
||||
BE_OBJ = $(BE_SRC:%.c=%.o)
|
||||
$(BE_PLUGIN): $(BE_OBJ)
|
||||
$(CC) -Wall -shared -o $@ -lc $<
|
||||
|
||||
# Secondary NACM backend plugin
|
||||
BE2_SRC = $(APPNAME)_backend_nacm.c
|
||||
BE2_OBJ = $(BE2_SRC:%.c=%.o)
|
||||
$(BE2_PLUGIN): $(BE2_OBJ)
|
||||
$(CC) -Wall -shared -o $@ -lc $<
|
||||
|
||||
# CLI frontend plugin
|
||||
CLI_SRC = $(APPNAME)_cli.c
|
||||
CLI_OBJ = $(CLI_SRC:%.c=%.o)
|
||||
$(CLI_PLUGIN): $(CLI_OBJ)
|
||||
$(CC) -Wall -shared -o $@ -lc $^
|
||||
|
||||
# NETCONF frontend plugin
|
||||
NETCONF_SRC = $(APPNAME)_netconf.c
|
||||
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
|
||||
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
|
||||
$(CC) -Wall -shared -o $@ -lc $^
|
||||
|
||||
# See configure.ac
|
||||
# RESTCONF frontend plugin
|
||||
RESTCONF_SRC = $(APPNAME)_restconf.c
|
||||
RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o)
|
||||
$(RESTCONF_PLUGIN): $(RESTCONF_OBJ)
|
||||
$(CC) -Wall -shared -o $@ -lc $^
|
||||
|
||||
SRC = $(BE_SRC) $(BE2_SRC) $(CLI_SRC) $(NETCONF_SRC)
|
||||
SRC += $(RESTCONF_SRC)
|
||||
|
||||
OBJS = $(BE_OBJ) $(BE2_OBJ) $(CLI_OBJ) $(NETCONF_OBJ)
|
||||
OBJS += $(RESTCONF_OBJ)
|
||||
|
||||
clean:
|
||||
rm -f $(PLUGINS) $(OBJS)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
install: $(YANGSPECS) $(CLISPECS) $(PLUGINS) $(APPNAME).xml
|
||||
install -d -m 0755 $(DESTDIR)$(sysconfdir)
|
||||
install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)
|
||||
# install -m 0644 $(APPNAME).xml $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
|
||||
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
|
||||
install -m 0644 $(YANGSPECS) $(DESTDIR)$(DESTDIR)$(CLIXON_DATADIR)
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
|
||||
install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend
|
||||
install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/netconf
|
||||
install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/netconf
|
||||
ifeq ($(with_restconf),yes)
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/restconf
|
||||
install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/restconf
|
||||
endif
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec
|
||||
install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec
|
||||
install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME)
|
||||
|
||||
uninstall:
|
||||
rm -rf $(DESTDIR)$(sysconfdir)/$(APPNAME).xml
|
||||
rm -rf $(DESTDIR)$(datarootdir)/$(APPNAME)
|
||||
rm -rf $(DESTDIR)$(localstatedir)/$(APPNAME)
|
||||
rm -rf $(DESTDIR)$(libdir)/$(APPNAME)
|
||||
|
||||
install-include:
|
||||
|
||||
depend:
|
||||
$(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend
|
||||
|
||||
#include .depend
|
||||
|
||||
325
example/main/README.md
Normal file
325
example/main/README.md
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
# Clixon main 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](../../yang/clixon-config@2018-10-21.yang) for the documentation of all available fields.
|
||||
* `clixon-example@2019-01-13.yang` The yang spec of the example.
|
||||
* `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 example RPC call (`example_client_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
|
||||
|
||||
|
||||
## Compile and run
|
||||
|
||||
Before you start,
|
||||
* Make [group setup](../../doc/FAQ.md#do-i-need-to-setup-anything-important)
|
||||
* Setup [restconf](../../doc/FAQ.md#how-do-i-use-restconf)
|
||||
|
||||
```
|
||||
cd example
|
||||
make && sudo make install
|
||||
```
|
||||
Start backend:
|
||||
```
|
||||
sudo clixon_backend -f /usr/local/etc/example.xml -s init
|
||||
```
|
||||
Edit cli:
|
||||
```
|
||||
clixon_cli -f /usr/local/etc/example.xml
|
||||
```
|
||||
Send netconf command:
|
||||
```
|
||||
clixon_netconf -f /usr/local/etc/example.xml
|
||||
```
|
||||
Start clixon restconf daemon
|
||||
```
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
|
||||
```
|
||||
Send restconf command
|
||||
```
|
||||
curl -G http://127.0.0.1/restconf/data
|
||||
```
|
||||
|
||||
## 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 xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
|
||||
<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>]]>]]>
|
||||
```
|
||||
|
||||
### 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"><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>]]>]]>
|
||||
```
|
||||
## Restconf
|
||||
|
||||
Setup a web/reverse-proxy server.
|
||||
For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default:
|
||||
```
|
||||
server {
|
||||
...
|
||||
location / {
|
||||
root /usr/share/nginx/html/restconf;
|
||||
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
|
||||
include fastcgi_params;
|
||||
}
|
||||
location /restconf {
|
||||
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
|
||||
include fastcgi_params;
|
||||
}
|
||||
location /streams {
|
||||
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
|
||||
include fastcgi_params;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
}
|
||||
```
|
||||
Start nginx daemon
|
||||
```
|
||||
sudo /etc/init.d/nginx start
|
||||
```
|
||||
Start the clixon restconf daemon
|
||||
```
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
|
||||
```
|
||||
then access using curl or wget:
|
||||
```
|
||||
curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type
|
||||
```
|
||||
|
||||
## Streams
|
||||
|
||||
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 xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
|
||||
<rpc-reply><ok/></rpc-reply>]]>]]>
|
||||
<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:
|
||||
```
|
||||
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 (restc)[../../apps/restconf/README.md].
|
||||
|
||||
|
||||
## 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:
|
||||
```
|
||||
clixon_cli -f /usr/local/etc/example.xml
|
||||
cli> rpc ipv4
|
||||
<rpc-reply><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>
|
||||
```
|
||||
Example using Netconf:
|
||||
```
|
||||
clixon_netconf -qf /usr/local/etc/example.xml
|
||||
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>ipv4</x></example></rpc>]]>]]>
|
||||
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>
|
||||
```
|
||||
Restconf (assuming nginx started):
|
||||
```
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data&
|
||||
curl -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":"ipv4"}}'
|
||||
{
|
||||
"clixon-example:output": {
|
||||
"x": "ipv4",
|
||||
"y": "42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Details
|
||||
|
||||
The example works by defining an RPC in clixon-example.yang:
|
||||
```
|
||||
rpc example {
|
||||
description "Some example input/output for testing RFC7950 7.14.
|
||||
RPC simply echoes the input for debugging.";
|
||||
input {
|
||||
leaf x {
|
||||
...
|
||||
```
|
||||
|
||||
In the CLI a netconf rpc call is constructed and sent to the backend: See `example_client_rpc()` in [example_cli.c] CLI plugin.
|
||||
|
||||
The clixon backend plugin [example_backend.c] reveives the netconf call and replies. This is made byregistering a callback handling handling the RPC:
|
||||
```
|
||||
static int
|
||||
example_rpc(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 */
|
||||
{
|
||||
/* code that echoes the request */
|
||||
return 0;
|
||||
}
|
||||
int
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
...
|
||||
rpc_callback_register(h, example_rpc, NULL, "example");
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
The state data is enabled by starting the backend with: `-- -s`.
|
||||
|
||||
## 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
|
||||
|
||||
See [../../docker/system] for instructions on how to build this example
|
||||
as a docker container.
|
||||
|
||||
## 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.
|
||||
The content of the API struct is different depending on what kind of plugin it is.
|
||||
The plugin init function may also include registering RPC functions, see below is for a backend.
|
||||
```
|
||||
static clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init,
|
||||
plugin_start,
|
||||
plugin_exit,
|
||||
.ca_reset=plugin_reset,/* reset for extra XML at startup*/
|
||||
.ca_statedata=plugin_statedata, /* statedata */
|
||||
.ca_upgrade=example_upgrade, /* upgrade configuration */
|
||||
.ca_trans_begin=NULL, /* trans begin */
|
||||
.ca_trans_validate=transaction_validate,/* trans validate */
|
||||
.ca_trans_complete=NULL, /* trans complete */
|
||||
.ca_trans_commit=transaction_commit, /* trans commit */
|
||||
.ca_trans_end=NULL, /* trans end */
|
||||
.ca_trans_abort=NULL /* trans abort */
|
||||
};
|
||||
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
/* Optional callback registration for RPC calls */
|
||||
rpc_callback_register(h, example_rpc, NULL, "example");
|
||||
/* Return plugin API */
|
||||
return &api; /* Return NULL on error */
|
||||
}
|
||||
```
|
||||
|
||||
Here is a corresponding example for a CLI plugin:
|
||||
```
|
||||
static clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init, /* init */
|
||||
NULL, /* start */
|
||||
NULL, /* exit */
|
||||
.ca_prompt=NULL, /* cli_prompthook_t */
|
||||
.ca_suspend=NULL, /* cligen_susp_cb_t */
|
||||
.ca_interrupt=NULL, /* cligen_interrupt_cb_t */
|
||||
};
|
||||
```
|
||||
142
example/main/clixon-example@2019-01-13.yang
Normal file
142
example/main/clixon-example@2019-01-13.yang
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
module clixon-example {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
revision 2019-01-13 {
|
||||
description
|
||||
"Released in Clixon 3.9";
|
||||
}
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-ip {
|
||||
prefix ip;
|
||||
}
|
||||
import iana-if-type {
|
||||
prefix ianaift;
|
||||
}
|
||||
/* Example interface type for tests, local callbacks, etc */
|
||||
identity eth {
|
||||
base if:interface-type;
|
||||
}
|
||||
identity loopback {
|
||||
base if:interface-type;
|
||||
}
|
||||
/* Translation function example - See also example_cli */
|
||||
list translate{
|
||||
key k;
|
||||
leaf k{
|
||||
type string;
|
||||
}
|
||||
leaf value{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* State data (not config) for the example application*/
|
||||
container state {
|
||||
config false;
|
||||
description "state data for the example application (must be here for example get operation)";
|
||||
leaf-list op {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* Example notification as used in RFC 5277 and RFC 8040 */
|
||||
notification event {
|
||||
description "Example notification event.";
|
||||
leaf event-class {
|
||||
type string;
|
||||
description "Event class identifier.";
|
||||
}
|
||||
container reportingEntity {
|
||||
description "Event specific information.";
|
||||
leaf card {
|
||||
type string;
|
||||
description "Line card identifier.";
|
||||
}
|
||||
}
|
||||
leaf severity {
|
||||
type string;
|
||||
description "Event severity description.";
|
||||
}
|
||||
}
|
||||
rpc client-rpc {
|
||||
description "Example local client-side RPC that is processed by the
|
||||
the netconf/restconf and not sent to the backend.
|
||||
This is a clixon implementation detail: some rpc:s
|
||||
are better processed by the client for API or perf reasons";
|
||||
input {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc empty {
|
||||
description "Smallest possible RPC with no input or output sections";
|
||||
}
|
||||
rpc optional {
|
||||
description "Small RPC with optional input and output";
|
||||
input {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc example {
|
||||
description "Some example input/output for testing RFC7950 7.14.
|
||||
RPC simply echoes the input for debugging.";
|
||||
input {
|
||||
leaf x {
|
||||
description
|
||||
"If a leaf in the input tree has a 'mandatory' statement with
|
||||
the value 'true', the leaf MUST be present in an RPC invocation.";
|
||||
type string;
|
||||
mandatory true;
|
||||
}
|
||||
leaf y {
|
||||
description
|
||||
"If a leaf in the input tree has a 'mandatory' statement with the
|
||||
value 'true', the leaf MUST be present in an RPC invocation.";
|
||||
type string;
|
||||
default "42";
|
||||
}
|
||||
leaf-list z {
|
||||
description
|
||||
"If a leaf-list in the input tree has one or more default
|
||||
values, the server MUST use these values (XXX not supported)";
|
||||
type string;
|
||||
}
|
||||
leaf w {
|
||||
description
|
||||
"If any node has a 'when' statement that would evaluate to
|
||||
'false',then this node MUST NOT be present in the input tree.
|
||||
(XXX not supported)";
|
||||
type string;
|
||||
when "/translate/k=5/value='w'";
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
leaf y {
|
||||
type string;
|
||||
}
|
||||
leaf z {
|
||||
type string;
|
||||
}
|
||||
leaf w {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
example/main/example.xml
Normal file
21
example/main/example.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>/usr/local/etc/example.xml</CLICON_CONFIGFILE>
|
||||
<CLICON_FEATURE>*:*</CLICON_FEATURE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_NETCONF_DIR>/usr/local/lib/example/netconf</CLICON_NETCONF_DIR>
|
||||
<CLICON_RESTCONF_DIR>/usr/local/lib/example/restconf</CLICON_RESTCONF_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/example/cli</CLICON_CLI_DIR>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/example/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_SOCK>/usr/local/var/example/example.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/example.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/example</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
|
||||
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
|
||||
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
||||
<CLICON_NACM_MODE>disabled</CLICON_NACM_MODE>
|
||||
</clixon-config>
|
||||
632
example/main/example_backend.c
Normal file
632
example/main/example_backend.c
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2, indicate
|
||||
your decision by deleting the provisions above and replace them with the
|
||||
notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
/* clicon */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* Clicon library functions. */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
/* These include signatures for plugin and transaction callbacks. */
|
||||
#include <clixon/clixon_backend.h>
|
||||
|
||||
/*! Variable to control if reset code is run.
|
||||
* The reset code inserts "extra XML" which assumes ietf-interfaces is
|
||||
* loaded, and this is not always the case.
|
||||
* Therefore, the backend must be started with -- -r to enable the reset function
|
||||
*/
|
||||
static int _reset = 0;
|
||||
|
||||
/*! Variable to control if state code is run
|
||||
* The state code adds extra non-config data
|
||||
* Therefore, the backend must be started with -- -s to enable the state function
|
||||
*/
|
||||
static int _state = 0;
|
||||
|
||||
/*! Variable to control upgrade callbacks.
|
||||
* If set, call test-case for upgrading ietf-interfaces, otherwise call
|
||||
* auto-upgrade
|
||||
*/
|
||||
static int _upgrade = 0;
|
||||
|
||||
/* forward */
|
||||
static int example_stream_timer_setup(clicon_handle h);
|
||||
|
||||
/*! This is called on validate (and commit). Check validity of candidate
|
||||
*/
|
||||
int
|
||||
transaction_validate(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
// transaction_print(stderr, td);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! This is called on commit. Identify modifications and adjust machine state
|
||||
*/
|
||||
int
|
||||
transaction_commit(clicon_handle h,
|
||||
transaction_data td)
|
||||
{
|
||||
cxobj *target = transaction_target(td); /* wanted XML tree */
|
||||
cxobj **vec = NULL;
|
||||
int i;
|
||||
size_t len;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
/* Get all added i/fs */
|
||||
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
|
||||
return -1;
|
||||
if (debug)
|
||||
for (i=0; i<len; i++) /* Loop over added i/fs */
|
||||
xml_print(stdout, vec[i]); /* Print the added interface */
|
||||
// done:
|
||||
if (vec)
|
||||
free(vec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Routing example notification timer handler. Here is where the periodic action is
|
||||
*/
|
||||
static int
|
||||
example_stream_timer(int fd,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
clicon_handle h = (clicon_handle)arg;
|
||||
|
||||
/* XXX Change to actual netconf notifications and namespace */
|
||||
if (stream_notify(h, "EXAMPLE", "<event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
|
||||
goto done;
|
||||
if (example_stream_timer_setup(h) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Set up example stream notification timer
|
||||
*/
|
||||
static int
|
||||
example_stream_timer_setup(clicon_handle h)
|
||||
{
|
||||
struct timeval t, t1;
|
||||
|
||||
gettimeofday(&t, NULL);
|
||||
t1.tv_sec = 5; t1.tv_usec = 0;
|
||||
timeradd(&t, &t1, &t);
|
||||
return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
|
||||
}
|
||||
|
||||
/*! Smallest possible RPC declaration for test
|
||||
* Yang/XML:
|
||||
* If the RPC operation invocation succeeded and no output parameters
|
||||
* are returned, the <rpc-reply> contains a single <ok/> element defined
|
||||
* in [RFC6241].
|
||||
*/
|
||||
static int
|
||||
empty_rpc(clicon_handle h, /* Clicon handle */
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg, /* client_entry */
|
||||
void *regarg) /* Argument given at register */
|
||||
{
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! More elaborate example RPC for testing
|
||||
* The RPC returns the incoming parameters
|
||||
*/
|
||||
static int
|
||||
example_rpc(clicon_handle h, /* Clicon handle */
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg, /* client_entry */
|
||||
void *regarg) /* Argument given at register */
|
||||
{
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! This will be called as a hook right after the original system copy-config
|
||||
*/
|
||||
static int
|
||||
example_copy_extra(clicon_handle h, /* Clicon handle */
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg, /* client_entry */
|
||||
void *regarg) /* Argument given at register */
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
fprintf(stderr, "%s\n", __FUNCTION__);
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Called to get state data from plugin
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] xtop XML tree, <config/> on entry.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xmldb_get
|
||||
* @note this example code returns requires this yang snippet:
|
||||
container state {
|
||||
config false;
|
||||
description "state data for example application";
|
||||
leaf-list op {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
* This yang snippet is present in clixon-example.yang for exampl.
|
||||
*/
|
||||
int
|
||||
example_statedata(clicon_handle h,
|
||||
char *xpath,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **xvec = NULL;
|
||||
|
||||
if (!_state)
|
||||
goto ok;
|
||||
/* Example of (static) statedata, real code would poll state
|
||||
* Note this state needs to be accomanied by yang snippet
|
||||
* above
|
||||
*/
|
||||
if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
|
||||
"<op>42</op>"
|
||||
"<op>41</op>"
|
||||
"<op>43</op>" /* should not be ordered */
|
||||
"</state>", NULL, &xstate) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Testcase upgrade function moving interfaces-state to interfaces
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xn XML tree to be updated
|
||||
* @param[in] ns Namespace of module (for info)
|
||||
* @param[in] from From revision on the form YYYYMMDD
|
||||
* @param[in] to To revision on the form YYYYMMDD (0 not in system)
|
||||
* @param[in] arg User argument given at rpc_callback_register()
|
||||
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
|
||||
* @retval 1 OK
|
||||
* @retval 0 Invalid
|
||||
* @retval -1 Error
|
||||
* @see clicon_upgrade_cb
|
||||
* @see test_upgrade_interfaces.sh
|
||||
* @see upgrade_2016
|
||||
* This example shows a two-step upgrade where the 2014 function does:
|
||||
* - Move /if:interfaces-state/if:interface/if:admin-status to
|
||||
* /if:interfaces/if:interface/
|
||||
* - Move /if:interfaces-state/if:interface/if:statistics to
|
||||
* /if:interfaces/if:interface/
|
||||
* - Rename /interfaces/interface/description to descr
|
||||
*/
|
||||
static int
|
||||
upgrade_2016(clicon_handle h,
|
||||
cxobj *xt,
|
||||
char *ns,
|
||||
uint32_t from,
|
||||
uint32_t to,
|
||||
void *arg,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_spec *yspec;
|
||||
yang_stmt *ym;
|
||||
cxobj **vec = NULL;
|
||||
cxobj *xc;
|
||||
cxobj *xi; /* xml /interfaces-states/interface node */
|
||||
cxobj *x;
|
||||
cxobj *xif; /* xml /interfaces/interface node */
|
||||
size_t vlen;
|
||||
int i;
|
||||
char *name;
|
||||
|
||||
/* Get Yang module for this namespace. Note it may not exist (if obsolete) */
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
|
||||
goto ok; /* shouldnt happen */
|
||||
clicon_debug(1, "%s module %s", __FUNCTION__, ym?ym->ys_argument:"none");
|
||||
/* Get all XML nodes with that namespace */
|
||||
if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
|
||||
goto done;
|
||||
for (i=0; i<vlen; i++){
|
||||
xc = vec[i];
|
||||
/* Iterate through interfaces-state */
|
||||
if (strcmp(xml_name(xc),"interfaces-state") == 0){
|
||||
/* Note you cannot delete or move xml objects directly under xc
|
||||
* in the loop (eg xi objects) but you CAN move children of xi
|
||||
*/
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xi), "interface"))
|
||||
continue;
|
||||
if ((name = xml_find_body(xi, "name")) == NULL)
|
||||
continue; /* shouldnt happen */
|
||||
/* Get corresponding /interfaces/interface entry */
|
||||
xif = xpath_first(xt, "/interfaces/interface[name=\"%s\"]", name);
|
||||
/* - Move /if:interfaces-state/if:interface/if:admin-status to
|
||||
* /if:interfaces/if:interface/ */
|
||||
if ((x = xml_find(xi, "admin-status")) != NULL && xif){
|
||||
if (xml_addsub(xif, x) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* - Move /if:interfaces-state/if:interface/if:statistics to
|
||||
* /if:interfaces/if:interface/*/
|
||||
if ((x = xml_find(xi, "statistics")) != NULL){
|
||||
if (xml_addsub(xif, x) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(xml_name(xc),"interfaces") == 0){
|
||||
/* Iterate through interfaces */
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xi), "interface"))
|
||||
continue;
|
||||
/* Rename /interfaces/interface/description to descr */
|
||||
if ((x = xml_find(xi, "description")) != NULL)
|
||||
if (xml_name_set(x, "descr") < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
if (vec)
|
||||
free(vec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Testcase upgrade function removing interfaces-state
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xn XML tree to be updated
|
||||
* @param[in] ns Namespace of module (for info)
|
||||
* @param[in] from From revision on the form YYYYMMDD
|
||||
* @param[in] to To revision on the form YYYYMMDD (0 not in system)
|
||||
* @param[in] arg User argument given at rpc_callback_register()
|
||||
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
|
||||
* @retval 1 OK
|
||||
* @retval 0 Invalid
|
||||
* @retval -1 Error
|
||||
* @see clicon_upgrade_cb
|
||||
* @see test_upgrade_interfaces.sh
|
||||
* @see upgrade_2016
|
||||
* The 2016 function does:
|
||||
* - Delete /if:interfaces-state
|
||||
* - Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr
|
||||
* - Change type /interfaces/interface/statistics/in-octets to decimal64 with
|
||||
* fraction-digits 3 and divide all values with 1000
|
||||
*/
|
||||
static int
|
||||
upgrade_2018(clicon_handle h,
|
||||
cxobj *xt,
|
||||
char *ns,
|
||||
uint32_t from,
|
||||
uint32_t to,
|
||||
void *arg,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_spec *yspec;
|
||||
yang_stmt *ym;
|
||||
cxobj **vec = NULL;
|
||||
cxobj *xc;
|
||||
cxobj *xi;
|
||||
cxobj *x;
|
||||
cxobj *xb;
|
||||
size_t vlen;
|
||||
int i;
|
||||
|
||||
/* Get Yang module for this namespace. Note it may not exist (if obsolete) */
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
|
||||
goto ok; /* shouldnt happen */
|
||||
clicon_debug(1, "%s module %s", __FUNCTION__, ym?ym->ys_argument:"none");
|
||||
/* Get all XML nodes with that namespace */
|
||||
if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
|
||||
goto done;
|
||||
for (i=0; i<vlen; i++){
|
||||
xc = vec[i];
|
||||
/* Delete /if:interfaces-state */
|
||||
if (strcmp(xml_name(xc), "interfaces-state") == 0)
|
||||
xml_purge(xc);
|
||||
/* Iterate through interfaces */
|
||||
else if (strcmp(xml_name(xc),"interfaces") == 0){
|
||||
/* Iterate through interfaces */
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xi), "interface"))
|
||||
continue;
|
||||
/* Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr */
|
||||
if ((x = xml_find(xi, "descr")) != NULL)
|
||||
if (xml_wrap(x, "docs") < 0)
|
||||
goto done;
|
||||
/* Change type /interfaces/interface/statistics/in-octets to
|
||||
* decimal64 with fraction-digits 3 and divide values with 1000
|
||||
*/
|
||||
if ((x = xpath_first(xi, "statistics/in-octets")) != NULL){
|
||||
if ((xb = xml_body_get(x)) != NULL){
|
||||
uint64_t u64;
|
||||
cbuf *cb = cbuf_new();
|
||||
parse_uint64(xml_value(xb), &u64, NULL);
|
||||
cprintf(cb, "%" PRIu64 ".%03d", u64/1000, (int)(u64%1000));
|
||||
xml_value_set(xb, cbuf_get(cb));
|
||||
cbuf_free(cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
if (vec)
|
||||
free(vec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Plugin state reset. Add xml or set state in backend machine.
|
||||
* Called in each backend plugin. plugin_reset is called after all plugins
|
||||
* have been initialized. This give the application a chance to reset
|
||||
* system state back to a base state.
|
||||
* This is generally done when a system boots up to
|
||||
* make sure the initial system state is well defined. This can be creating
|
||||
* default configuration files for various daemons, set interface flags etc.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database. Not may be other than "running"
|
||||
* In this example, a loopback interface is added
|
||||
* @note This assumes example yang with interfaces/interface
|
||||
*/
|
||||
int
|
||||
example_reset(clicon_handle h,
|
||||
const char *db)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xt = NULL;
|
||||
int ret;
|
||||
cbuf *cbret = NULL;
|
||||
|
||||
if (!_reset)
|
||||
goto ok; /* Note not enabled by default */
|
||||
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 first child */
|
||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||
goto done;
|
||||
if ((cbret = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
/* Merge user reset state */
|
||||
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
|
||||
cbuf_get(cbret));
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (cbret)
|
||||
cbuf_free(cbret);
|
||||
if (xt != NULL)
|
||||
xml_free(xt);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Plugin start.
|
||||
* @param[in] h Clicon handle
|
||||
*
|
||||
* plugin_start is called once everything has been initialized, right before
|
||||
* the main event loop is entered.
|
||||
*/
|
||||
int
|
||||
example_start(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
example_exit(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
|
||||
|
||||
static clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init, /* init - must be called clixon_plugin_init */
|
||||
example_start, /* start */
|
||||
example_exit, /* exit */
|
||||
.ca_reset=example_reset, /* reset */
|
||||
.ca_statedata=example_statedata, /* statedata */
|
||||
.ca_trans_begin=NULL, /* trans begin */
|
||||
.ca_trans_validate=transaction_validate,/* trans validate */
|
||||
.ca_trans_complete=NULL, /* trans complete */
|
||||
.ca_trans_commit=transaction_commit, /* trans commit */
|
||||
.ca_trans_end=NULL, /* trans end */
|
||||
.ca_trans_abort=NULL /* trans abort */
|
||||
};
|
||||
|
||||
/*! Backend plugin initialization
|
||||
* @param[in] h Clixon handle
|
||||
* @retval NULL Error with clicon_err set
|
||||
* @retval api Pointer to API struct
|
||||
* In this example, you can pass -r, -s, -u to control the behaviour, mainly
|
||||
* for use in the test suites.
|
||||
*/
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
struct timeval retention = {0,0};
|
||||
int argc; /* command-line options (after --) */
|
||||
char **argv;
|
||||
char c;
|
||||
|
||||
clicon_debug(1, "%s backend", __FUNCTION__);
|
||||
|
||||
/* Get user command-line options (after --) */
|
||||
if (clicon_argv_get(h, &argc, &argv) < 0)
|
||||
goto done;
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, "rsu")) != -1)
|
||||
switch (c) {
|
||||
case 'r':
|
||||
_reset = 1;
|
||||
break;
|
||||
case 's':
|
||||
_state = 1;
|
||||
break;
|
||||
case 'u':
|
||||
_upgrade = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Example stream initialization:
|
||||
* 1) Register EXAMPLE stream
|
||||
* 2) setup timer for notifications, so something happens on stream
|
||||
* 3) setup stream callbacks for notification to push channel
|
||||
*/
|
||||
if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
|
||||
retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
|
||||
if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
|
||||
goto done;
|
||||
/* Enable nchan pub/sub streams
|
||||
* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
|
||||
*/
|
||||
if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
|
||||
stream_publish(h, "EXAMPLE") < 0)
|
||||
goto done;
|
||||
if (example_stream_timer_setup(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Register callback for routing rpc calls
|
||||
*/
|
||||
/* From example.yang (clicon) */
|
||||
if (rpc_callback_register(h, empty_rpc,
|
||||
NULL,
|
||||
"urn:example:clixon",
|
||||
"empty"/* Xml tag when callback is made */
|
||||
) < 0)
|
||||
goto done;
|
||||
/* Same as example but with optional input/output */
|
||||
if (rpc_callback_register(h, example_rpc,
|
||||
NULL,
|
||||
"urn:example:clixon",
|
||||
"optional"/* Xml tag when callback is made */
|
||||
) < 0)
|
||||
goto done;
|
||||
/* Same as example but with optional input/output */
|
||||
if (rpc_callback_register(h, example_rpc,
|
||||
NULL,
|
||||
"urn:example:clixon",
|
||||
"example"/* Xml tag when callback is made */
|
||||
) < 0)
|
||||
goto done;
|
||||
/* Called after the regular system copy_config callback */
|
||||
if (rpc_callback_register(h, example_copy_extra,
|
||||
NULL,
|
||||
"urn:ietf:params:xml:ns:netconf:base:1.0",
|
||||
"copy-config"
|
||||
) < 0)
|
||||
goto done;
|
||||
/* Upgrade callback: if you start the backend with -- -u you will get the
|
||||
* test interface example. Otherwise the auto-upgrade feature is enabled.
|
||||
*/
|
||||
if (_upgrade){
|
||||
if (upgrade_callback_register(h, upgrade_2016, "urn:example:interfaces", 20140508, 20160101, NULL) < 0)
|
||||
goto done;
|
||||
if (upgrade_callback_register(h, upgrade_2018, "urn:example:interfaces", 20160101, 20180220, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
else
|
||||
if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
/* Return plugin API */
|
||||
return &api;
|
||||
done:
|
||||
return NULL;
|
||||
}
|
||||
125
example/main/example_backend_nacm.c
Normal file
125
example/main/example_backend_nacm.c
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2, indicate
|
||||
your decision by deleting the provisions above and replace them with the
|
||||
notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*
|
||||
* IETF yang routing example
|
||||
* Secondary backend for testing more than one backend plugin
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/syslog.h>
|
||||
|
||||
/* clicon */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* Clicon library functions. */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
/* These include signatures for plugin and transaction callbacks. */
|
||||
#include <clixon/clixon_backend.h>
|
||||
|
||||
|
||||
/*! Called to get NACM state data
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] xtop XML tree, <config/> on entry.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xmldb_get
|
||||
* @note this example code returns a static statedata used in testing.
|
||||
* Real code would poll state
|
||||
*/
|
||||
int
|
||||
nacm_statedata(clicon_handle h,
|
||||
char *xpath,
|
||||
cxobj *xstate)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj **xvec = NULL;
|
||||
|
||||
/* Example of (static) statedata, real code would poll state */
|
||||
if (xml_parse_string("<nacm xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-acm\">"
|
||||
"<denied-data-writes>0</denied-data-writes>"
|
||||
"<denied-operations>0</denied-operations>"
|
||||
"<denied-notifications>0</denied-notifications>"
|
||||
"</nacm>", NULL, &xstate) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
plugin_start(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
|
||||
|
||||
static clixon_plugin_api api = {
|
||||
"nacm", /* name */ /*--- Common fields. ---*/
|
||||
clixon_plugin_init, /* init */
|
||||
plugin_start, /* start */
|
||||
NULL, /* exit */
|
||||
.ca_reset=NULL, /* reset */
|
||||
.ca_statedata=nacm_statedata, /* statedata */
|
||||
};
|
||||
|
||||
/*! Backend plugin initialization
|
||||
* @param[in] h Clixon handle
|
||||
* @retval NULL Error with clicon_err set
|
||||
* @retval api Pointer to API struct
|
||||
*/
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
char *nacm_mode;
|
||||
|
||||
clicon_debug(1, "%s backend nacm", __FUNCTION__);
|
||||
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
|
||||
clicon_log(LOG_DEBUG, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
|
||||
return NULL;
|
||||
}
|
||||
return &api;
|
||||
}
|
||||
169
example/main/example_cli.c
Normal file
169
example/main/example_cli.c
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
#include <fnmatch.h> /* matching strings */
|
||||
#include <signal.h> /* matching strings */
|
||||
|
||||
/* clicon */
|
||||
#include <cligen/cligen.h>
|
||||
#include <clixon/clixon.h>
|
||||
#include <clixon/clixon_cli.h>
|
||||
|
||||
|
||||
/*! Example cli function */
|
||||
int
|
||||
mycallback(clicon_handle h, cvec *cvv, cvec *argv)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xret = NULL;
|
||||
cg_var *myvar;
|
||||
|
||||
/* Access cligen callback variables */
|
||||
myvar = cvec_find(cvv, "var"); /* get a cligen variable from vector */
|
||||
fprintf(stderr, "%s: %d\n", __FUNCTION__, cv_int32_get(myvar)); /* get int value */
|
||||
fprintf(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */
|
||||
|
||||
/* Show eth0 interfaces config using XPATH */
|
||||
if (clicon_rpc_get_config(h, "running",
|
||||
"/interfaces/interface[name='eth0']",
|
||||
&xret) < 0)
|
||||
goto done;
|
||||
xml_print(stdout, xret);
|
||||
retval = 0;
|
||||
done:
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Example "downcall", ie initiate an RPC to the backend */
|
||||
int
|
||||
example_client_rpc(clicon_handle h,
|
||||
cvec *cvv,
|
||||
cvec *argv)
|
||||
{
|
||||
int retval = -1;
|
||||
cg_var *cva;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xrpc;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xerr;
|
||||
|
||||
/* User supplied variable in CLI command */
|
||||
cva = cvec_find(cvv, "a"); /* get a cligen variable from vector */
|
||||
/* Create XML for example netconf RPC */
|
||||
if (xml_parse_va(&xtop, NULL, "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" username=\"%s\"><example xmlns=\"urn:example:clixon\"><x>%s</x></example></rpc>",
|
||||
clicon_username_get(h),
|
||||
cv_string_get(cva)) < 0)
|
||||
goto done;
|
||||
/* Skip top-level */
|
||||
xrpc = xml_child_i(xtop, 0);
|
||||
/* Send to backend */
|
||||
if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0)
|
||||
goto done;
|
||||
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
|
||||
clicon_rpc_generate_error("Get configuration", xerr);
|
||||
goto done;
|
||||
}
|
||||
/* Print result */
|
||||
clicon_xml2file(stdout, xml_child_i(xret, 0), 0, 0);
|
||||
fprintf(stdout,"\n");
|
||||
|
||||
/* pretty-print:
|
||||
xml2txt(stdout, xml_child_i(xret, 0), 0);
|
||||
*/
|
||||
retval = 0;
|
||||
done:
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init, /* init */
|
||||
NULL, /* start */
|
||||
NULL, /* exit */
|
||||
.ca_prompt=NULL, /* cli_prompthook_t */
|
||||
.ca_suspend=NULL, /* cligen_susp_cb_t */
|
||||
.ca_interrupt=NULL, /* cligen_interrupt_cb_t */
|
||||
};
|
||||
|
||||
/*! CLI plugin initialization
|
||||
* @param[in] h Clixon handle
|
||||
* @retval NULL Error with clicon_err set
|
||||
* @retval api Pointer to API struct
|
||||
*/
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
srandom(tv.tv_usec);
|
||||
|
||||
return &api;
|
||||
}
|
||||
|
||||
/*! Translate function from an original value to a new.
|
||||
* In this case, assume string and increment characters, eg HAL->IBM
|
||||
*/
|
||||
int
|
||||
incstr(cligen_handle h,
|
||||
cg_var *cv)
|
||||
{
|
||||
char *str;
|
||||
int i;
|
||||
|
||||
if (cv_type_get(cv) != CGV_STRING)
|
||||
return 0;
|
||||
str = cv_string_get(cv);
|
||||
for (i=0; i<strlen(str); i++)
|
||||
str[i]++;
|
||||
return 0;
|
||||
}
|
||||
69
example/main/example_cli.cli
Normal file
69
example/main/example_cli.cli
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Common CLI syntax for both server and PMNode operatio mode
|
||||
CLICON_MODE="example";
|
||||
CLICON_PROMPT="%U@%H> ";
|
||||
CLICON_PLUGIN="example_cli";
|
||||
|
||||
# Translate variable "value" by incrementing its characters
|
||||
translate value (<value:string translate:incstr()>),cli_set("/translate/value");
|
||||
|
||||
# Note, when switching to PT, change datamodel to only @datamodel
|
||||
set @datamodel, cli_set();
|
||||
merge @datamodel, cli_merge();
|
||||
create @datamodel, cli_create();
|
||||
delete("Delete a configuration item") @datamodel, cli_del();
|
||||
|
||||
validate("Validate changes"), cli_validate();
|
||||
commit("Commit the changes"), cli_commit();
|
||||
quit("Quit"), cli_quit();
|
||||
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate");
|
||||
|
||||
startup("Store running as startup config"), db_copy("running", "startup");
|
||||
no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cli((int32)0);
|
||||
debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
|
||||
level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_backend();
|
||||
}
|
||||
copy("Copy and create a new object") {
|
||||
interface("Copy interface"){
|
||||
<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();
|
||||
compare("Compare running and candidate"), compare_dbs((int32)1);
|
||||
|
||||
show("Show a particular state of the system"){
|
||||
xpath("Show configuration") <xpath:string>("XPATH expression"), show_conf_xpath("candidate");
|
||||
version("Show version"), cli_show_version("candidate", "text", "/");
|
||||
compare("Compare candidate and running databases"), compare_dbs((int32)0);{
|
||||
xml("Show comparison in xml"), compare_dbs((int32)0);
|
||||
text("Show comparison in text"), compare_dbs((int32)1);
|
||||
}
|
||||
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
|
||||
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
|
||||
@datamodel, cli_show_auto("candidate", "xml");
|
||||
}
|
||||
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{
|
||||
@datamodel, cli_show_auto("candidate", "cli");
|
||||
}
|
||||
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{
|
||||
@datamodel, cli_show_auto("candidate", "netconf");
|
||||
}
|
||||
text("Show configuration as text"), cli_show_config("candidate","text","/");{
|
||||
@datamodel, cli_show_auto("candidate", "text");
|
||||
}
|
||||
json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{
|
||||
@datamodel, cli_show_auto("candidate", "json");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename");
|
||||
load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_file("filename", "replace");{
|
||||
replace("Replace candidate with file contents"), load_config_file("filename", "replace");
|
||||
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
|
||||
}
|
||||
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
|
||||
rpc("example rpc") <a:string>("routing instance"), example_client_rpc("");
|
||||
notify("Get notifications from backend"), cli_notify("EXAMPLE", "1", "text");
|
||||
no("Negate") notify("Get notifications from backend"), cli_notify("EXAMPLE", "0", "xml");
|
||||
lock,cli_lock("candidate");
|
||||
unlock,cli_unlock("candidate");
|
||||
123
example/main/example_netconf.c
Normal file
123
example/main/example_netconf.c
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2, indicate
|
||||
your decision by deleting the provisions above and replace them with the
|
||||
notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include <cligen/cligen.h>
|
||||
#include <clixon/clixon.h>
|
||||
#include <clixon/clixon_netconf.h>
|
||||
|
||||
/*! Plugin start
|
||||
* Called once everything has been initialized, right before
|
||||
* the main event loop is entered.
|
||||
*/
|
||||
int
|
||||
plugin_start(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
plugin_exit(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Local example netconf rpc callback
|
||||
*/
|
||||
int netconf_client_rpc(clicon_handle h,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
clixon_plugin_api * clixon_plugin_init(clicon_handle h);
|
||||
|
||||
static struct clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init, /* init */
|
||||
plugin_start, /* start */
|
||||
plugin_exit /* exit */
|
||||
};
|
||||
|
||||
/*! Netconf plugin initialization
|
||||
* @param[in] h Clixon handle
|
||||
* @retval NULL Error with clicon_err set
|
||||
* @retval api Pointer to API struct
|
||||
*/
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
clicon_debug(1, "%s restconf", __FUNCTION__);
|
||||
/* Register local netconf rpc client (note not backend rpc client) */
|
||||
if (rpc_callback_register(h, netconf_client_rpc, NULL,
|
||||
"urn:example:clixon", "client-rpc") < 0)
|
||||
return NULL;
|
||||
return &api;
|
||||
}
|
||||
|
||||
352
example/main/example_restconf.c
Normal file
352
example/main/example_restconf.c
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include <fcgiapp.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clixon/clixon.h>
|
||||
#include <clixon/clixon_restconf.h>
|
||||
|
||||
static const char Base64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const char Pad64 = '=';
|
||||
|
||||
/* Use http basic auth. Set by starting restonf with:
|
||||
* clixon_restconf ... -- -a
|
||||
*/
|
||||
static int basic_auth = 0;
|
||||
|
||||
/* skips all whitespace anywhere.
|
||||
converts characters, four at a time, starting at (or after)
|
||||
src from base - 64 numbers into three 8 bit bytes in the target area.
|
||||
it returns the number of data bytes stored at the target, or -1 on error.
|
||||
@note what is copyright of this?
|
||||
*/
|
||||
int
|
||||
b64_decode(const char *src,
|
||||
char *target,
|
||||
size_t targsize)
|
||||
{
|
||||
int tarindex, state, ch;
|
||||
char *pos;
|
||||
|
||||
state = 0;
|
||||
tarindex = 0;
|
||||
|
||||
while ((ch = *src++) != '\0') {
|
||||
if (isspace(ch)) /* Skip whitespace anywhere. */
|
||||
continue;
|
||||
|
||||
if (ch == Pad64)
|
||||
break;
|
||||
|
||||
pos = strchr(Base64, ch);
|
||||
if (pos == 0) /* A non-base64 character. */
|
||||
return (-1);
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (target) {
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] = (pos - Base64) << 2;
|
||||
}
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (target) {
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64) >> 4;
|
||||
target[tarindex+1] = ((pos - Base64) & 0x0f)
|
||||
<< 4 ;
|
||||
}
|
||||
tarindex++;
|
||||
state = 2;
|
||||
break;
|
||||
case 2:
|
||||
if (target) {
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64) >> 2;
|
||||
target[tarindex+1] = ((pos - Base64) & 0x03)
|
||||
<< 6;
|
||||
}
|
||||
tarindex++;
|
||||
state = 3;
|
||||
break;
|
||||
case 3:
|
||||
if (target) {
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64);
|
||||
}
|
||||
tarindex++;
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We are done decoding Base-64 chars. Let's see if we ended
|
||||
* on a byte boundary, and/or with erroneous trailing characters.
|
||||
*/
|
||||
|
||||
if (ch == Pad64) { /* We got a pad char. */
|
||||
ch = *src++; /* Skip it, get next. */
|
||||
switch (state) {
|
||||
case 0: /* Invalid = in first position */
|
||||
case 1: /* Invalid = in second position */
|
||||
return (-1);
|
||||
|
||||
case 2: /* Valid, means one byte of info */
|
||||
/* Skip any number of spaces. */
|
||||
for ((void)NULL; ch != '\0'; ch = *src++)
|
||||
if (!isspace(ch))
|
||||
break;
|
||||
/* Make sure there is another trailing = sign. */
|
||||
if (ch != Pad64)
|
||||
return (-1);
|
||||
ch = *src++; /* Skip the = */
|
||||
/* Fall through to "single trailing =" case. */
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case 3: /* Valid, means two bytes of info */
|
||||
/*
|
||||
* We know this char is an =. Is there anything but
|
||||
* whitespace after it?
|
||||
*/
|
||||
for ((void)NULL; ch != '\0'; ch = *src++)
|
||||
if (!isspace(ch))
|
||||
return (-1);
|
||||
|
||||
/*
|
||||
* Now make sure for cases 2 and 3 that the "extra"
|
||||
* bits that slopped past the last full byte were
|
||||
* zeros. If we don't check them, they become a
|
||||
* subliminal channel.
|
||||
*/
|
||||
if (target && target[tarindex] != 0)
|
||||
return (-1);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We ended by seeing the end of the string. Make sure we
|
||||
* have no partial bytes lying around.
|
||||
*/
|
||||
if (state != 0)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (tarindex);
|
||||
}
|
||||
|
||||
/*! Process a rest request that requires (cookie) "authentication"
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] arg Argument. Here: Fastcgi request handle
|
||||
* @retval -1 Fatal error
|
||||
* @retval 0 Unauth
|
||||
* @retval 1 Auth
|
||||
* @note: Three hardwired users: andy, wilma, guest w password "bar".
|
||||
* Enabled by passing -- -a to the main function
|
||||
*/
|
||||
int
|
||||
example_restconf_credentials(clicon_handle h,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
FCGX_Request *r = (FCGX_Request *)arg;
|
||||
cxobj *xt = NULL;
|
||||
char *auth;
|
||||
char *user = NULL;
|
||||
char *passwd;
|
||||
char *passwd2 = "";
|
||||
size_t authlen;
|
||||
cbuf *cb = NULL;
|
||||
int ret;
|
||||
|
||||
/* HTTP basic authentication not enabled, pass with user "none" */
|
||||
if (basic_auth==0)
|
||||
goto ok;
|
||||
/* At this point in the code we must use HTTP basic authentication */
|
||||
if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL)
|
||||
goto fail;
|
||||
if (strlen(auth) < strlen("Basic "))
|
||||
goto fail;
|
||||
if (strncmp("Basic ", auth, strlen("Basic ")))
|
||||
goto fail;
|
||||
auth += strlen("Basic ");
|
||||
authlen = strlen(auth)*2;
|
||||
if ((user = malloc(authlen)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(user, 0, authlen);
|
||||
if ((ret = b64_decode(auth, user, authlen)) < 0)
|
||||
goto done;
|
||||
/* auth string is on the format user:passwd */
|
||||
if ((passwd = index(user,':')) == NULL)
|
||||
goto fail;
|
||||
*passwd = '\0';
|
||||
passwd++;
|
||||
clicon_debug(1, "%s http user:%s passwd:%s", __FUNCTION__, user, passwd);
|
||||
/* Here get auth sub-tree whjere all the users are */
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
/* XXX Three hardcoded user/passwd (from RFC8341 A.1)*/
|
||||
if (strcmp(user, "wilma")==0 || strcmp(user, "andy")==0 ||
|
||||
strcmp(user, "guest")==0){
|
||||
passwd2 = "bar";
|
||||
}
|
||||
if (strcmp(passwd, passwd2))
|
||||
goto fail;
|
||||
retval = 1;
|
||||
clicon_debug(1, "%s user:%s", __FUNCTION__, user);
|
||||
if (clicon_username_set(h, user) < 0)
|
||||
goto done;
|
||||
ok: /* authenticated */
|
||||
retval = 1;
|
||||
done: /* error */
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (user)
|
||||
free(user);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
return retval;
|
||||
fail: /* unauthenticated */
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Local example restconf rpc callback
|
||||
*/
|
||||
int
|
||||
restconf_client_rpc(clicon_handle h,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Start example restonf plugin. Set authentication method
|
||||
*/
|
||||
int
|
||||
example_restconf_start(clicon_handle h)
|
||||
{
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clixon_plugin_api * clixon_plugin_init(clicon_handle h);
|
||||
|
||||
static clixon_plugin_api api = {
|
||||
"example", /* name */
|
||||
clixon_plugin_init, /* init */
|
||||
example_restconf_start,/* start */
|
||||
NULL, /* exit */
|
||||
.ca_auth=example_restconf_credentials /* auth */
|
||||
};
|
||||
|
||||
/*! Restconf plugin initialization
|
||||
* @param[in] h Clixon handle
|
||||
* @retval NULL Error with clicon_err set
|
||||
* @retval api Pointer to API struct
|
||||
* Arguments are argc/argv after --
|
||||
* Currently defined: -a enable http basic authentication
|
||||
* @note There are three hardwired users andy, wilma and guest from RFC8341 A.1
|
||||
*/
|
||||
clixon_plugin_api *
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
int argc; /* command-line options (after --) */
|
||||
char **argv = NULL;
|
||||
char c;
|
||||
|
||||
clicon_debug(1, "%s restconf", __FUNCTION__);
|
||||
/* Get user command-line options (after --) */
|
||||
if (clicon_argv_get(h, &argc, &argv) < 0)
|
||||
return NULL;
|
||||
opterr = 0;
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, "a")) != -1)
|
||||
switch (c) {
|
||||
case 'a':
|
||||
basic_auth = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Register local netconf rpc client (note not backend rpc client) */
|
||||
if (rpc_callback_register(h, restconf_client_rpc, NULL, "urn:example:clixon", "client-rpc") < 0)
|
||||
return NULL;
|
||||
return &api;
|
||||
}
|
||||
13
example/main/systemd/example.service
Normal file
13
example/main/systemd/example.service
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Starts and stops a clixon example service on this system
|
||||
Wants=example_restconf.service
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
User=root
|
||||
RestartSec=60
|
||||
Restart=on-failure
|
||||
ExecStart=/usr/local/sbin/clixon_backend -s running -f /usr/local/etc/example.xml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
14
example/main/systemd/example_restconf.service
Normal file
14
example/main/systemd/example_restconf.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Starts and stops an example clixon restconf service on this system
|
||||
Wants=example.service
|
||||
After=example.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/www-data
|
||||
Restart=on-failure
|
||||
ExecStart=/www-data/clixon_restconf -f /usr/local/etc/example.xml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Add table
Add a link
Reference in a new issue