Skip to content

Commit

Permalink
Controller RESTCONF: fixed GET and PUT across mountpoints
Browse files Browse the repository at this point in the history
See [Controller RESTCONF access not properly tested and documented](#167)
  • Loading branch information
olofhagsand committed Feb 24, 2025
1 parent e90412b commit 0f8140a
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 11 deletions.
14 changes: 12 additions & 2 deletions src/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,16 @@ CLI_OBJ = $(CLI_SRC:%.c=%.o)
$(CLI_PLUGIN): $(CLI_OBJ) $(GENOBJS)
$(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $^ -lclixon -lclixon_cli

OBJS = $(BE_OBJ) $(CLI_OBJ)
PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN)
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
RESTCONF_SRC = $(APPNAME)_restconf.c
RESTCONF_SRC += controller_lib.c
RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o)

$(RESTCONF_PLUGIN): $(RESTCONF_OBJ) $(GENOBJS)
$(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $^ -lclixon -lclixon_restconf

OBJS = $(BE_OBJ) $(CLI_OBJ) $(RESTCONF_OBJ)
PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(RESTCONF_PLUGIN)

.SUFFIXES: .c .o

Expand Down Expand Up @@ -121,12 +129,14 @@ install: $(CLISPECS) $(PLUGINS) $(APPNAME).xml autocli.xml
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/restconf
install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -d -m 0755 $(DESTDIR)$(localstatedir)
install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME)
install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME)/pipe
install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend
install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli
install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/restconf
install -d -m 0775 -o @CLICON_USER@ -g @CLICON_GROUP@ $(DESTDIR)$(runstatedir)/controller
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/pipe

Expand Down
1 change: 1 addition & 0 deletions src/controller.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<!-- RESTCONF -->
<CLICON_RESTCONF_INSTALLDIR>@SBINDIR@</CLICON_RESTCONF_INSTALLDIR>
<CLICON_RESTCONF_USER>@CLICON_USER@</CLICON_RESTCONF_USER>
<CLICON_RESTCONF_DIR>@LIBDIR@/controller/restconf</CLICON_RESTCONF_DIR>
<!-- CLI/CLISPEC -->
<CLICON_CLI_DIR>@LIBDIR@/controller/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>@LIBDIR@/controller/clispec</CLICON_CLISPEC_DIR>
Expand Down
235 changes: 235 additions & 0 deletions src/controller_restconf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2025 Olof Hagsand
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.
***** END LICENSE BLOCK *****
*
* Main backend plugin file
*/

#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 <syslog.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <sys/time.h>
#include <sys/stat.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>

/* Controller includes */
#include "controller.h"
#include "controller_lib.h"
#include "controller_device_state.h"
#include "controller_device_handle.h"
#include "controller_device_send.h"
#include "controller_transaction.h"
#include "controller_rpc.h"


/*! Called when application is "started", (almost) all initialization is complete
*
* Create a global transaction notification handler and socket
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error
*/
int
controller_restconf_start(clixon_handle h)
{
return 0;
}

/*! Called just before plugin unloaded.
*
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error
*/
int
controller_restconf_exit(clixon_handle h)
{
return 0;
}

/*! Callback for printing version output and exit
*
* XXX unsure if this is ever called for restconf?
*/
int
controller_restconf_version(clixon_handle h,
FILE *f)
{
/* Assume clixon version already printed */
fprintf(f, "Controller:\t%s\n", CONTROLLER_VERSION);
fprintf(f, "Build:\t\t%s\n", CONTROLLER_BUILDSTR);
return 0;
}

/*! Get yanglib from xpath and nsc of mountpoint
*
* @param[in] h Clixon handle
* @param[in] xpath XPath of mountpoint
* @param[in] nsc Namespace context of xpath
* @param[out] xylib New XML tree on the form <yang-library><module-set><module>*, caller frees
* @retval 1 OK
* @retval 0 Skip
* @retval -1 Error
*/
static int
xpath2yanglib(clixon_handle h,
char *xpath,
cvec *nsc,
cxobj **xylibp)
{
int retval = -1;
cbuf *cb = NULL;
cxobj *xt = NULL;
cxobj *xerr;
cxobj *xylib = NULL;

if ((cb = cbuf_new()) == NULL){
clixon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "%s", xpath);
/* xpath to mount-point (to get config) */
/* XXX: why not use /yanglib:yang-library directly ?
* A: because you cannot get state-only (across mount-point?)
*/
if (clicon_rpc_get2(h, cbuf_get(cb), nsc, CONTENT_ALL, -1, "explicit", 0, &xt) < 0)
goto done;
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
clixon_err_netconf(h, OE_XML, 0, xerr, "clicon_rpc_get");
goto done;
}
/* xpath to module-set */
if (xml_nsctx_add(nsc, "yanglib", "urn:ietf:params:xml:ns:yang:ietf-yang-library") < 0)
goto done;
/* Append yang-library cb */
cprintf(cb, "/yanglib:yang-library");
if ((xylib = xpath_first(xt, nsc, "%s", cbuf_get(cb))) == NULL)
goto skip;
xml_rm(xylib);
if (controller_yang_library_bind(h, xylib) < 0)
goto done;
*xylibp = xylib;
xylib = NULL;
retval = 1;
done:
if (xylib)
xml_free(xylib);
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
skip:
retval = 0;
goto done;
}

/*! YANG schema mount, query backend of yangs
*
* Given an XML mount-point xt, return XML yang-lib modules-set
* Return yanglib as XML tree on the RFC8525 form:
* <yang-library>
* <module-set>
* <module>...</module>
* ...
* </module-set>
* </yang-library>
* Get the schema-list for this device from the backend
* @param[in] h Clixon handle
* @param[in] xmt XML mount-point in XML tree
* @param[out] config If '0' all data nodes in the mounted schema are read-only
* @param[out] vallevel Do or dont do full RFC 7950 validation
* @param[out] yanglib XML yang-lib module-set tree. Freed by caller.
* @retval 0 OK
* @retval -1 Error
* @see RFC 8528 (schema-mount) and RFC 8525 (yang-lib)
* @see device_send_get_schema_list/device_recv_schema_list Backend fns for send/rcv
*/
int
controller_restconf_yang_mount(clixon_handle h,
cxobj *xmt,
int *config,
validate_level *vl,
cxobj **xyanglib)
{
int retval = -1;
char *xpath = NULL;
cvec *nsc = NULL;
int ret;

if (xml_nsctx_node(xmt, &nsc) < 0)
goto done;
if (xml2xpath(xmt, nsc, 1, 1, &xpath) < 0)
goto done;
/* get modset */
ret = xpath2yanglib(h, xpath, nsc, xyanglib);
if (ret < 0)
goto done;
if (ret == 0)
goto ok;
ok:
retval = 0;
done:
if (xpath)
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
return retval;
}

/*! Forward declaration */
clixon_plugin_api *clixon_plugin_init(clixon_handle h);

static clixon_plugin_api api = {
"controller restconf",
.ca_start = controller_restconf_start,
.ca_exit = controller_restconf_exit,
.ca_yang_mount = controller_restconf_yang_mount,
.ca_version = controller_restconf_version,
};

clixon_plugin_api *
clixon_plugin_init(clixon_handle h)
{
if (!clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
clixon_err(OE_YANG, 0, "The clixon controller requires CLICON_YANG_SCHEMA_MOUNT set to true");
goto done;
}
return &api;
done:
return NULL;
}
26 changes: 17 additions & 9 deletions test/test-restconf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ if [ $? -ne 0 ]; then
err1 "Error when generating certs"
fi

# To debug, set CLICON_BACKEND_RESTCONF_PROCESS=false and start clixon_restconf manually
# clixon_restconf -f /usr/local/etc/clixon/controller.xml -E /var/tmp/./test-restconf.sh/confdir -o CLICON_BACKEND_RESTCONF_PROCESS=true
# Specialize controller.xml
cat<<EOF > $CFD/diff.xml
<?xml version="1.0" encoding="utf-8"?>
Expand Down Expand Up @@ -224,25 +226,31 @@ new "restconf get restconf resource. RFC 8040 3.3 (xml)"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 "HTTP/$HVER 200" '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2019-01-04</yang-library-version></restconf>'

new "restconf GET datastore top"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '"clixon-controller:devices":{"device":\[{"name":"openconfig1"' '"config":{"interfaces":{"interface":\[{"name":"x","config":{"name":"x","type":"ianaift:ethernetCsmacd"' '"ietf-netconf-monitoring:netconf-state":{"capabilities":{'
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 200" '"clixon-controller:devices":{"device":\[{"name":"openconfig1"' '"config":{"openconfig-interfaces:interfaces":{"interface":\[{"name":"x","config":{"name":"x","type":"iana-if-type:ethernetCsmacd"' '"ietf-netconf-monitoring:netconf-state":{"capabilities":{'

new "restconf GET device config"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config)" 0 "HTTP/$HVER 200" '"clixon-controller:config":{"interfaces":{"interface":\[{"name":"x","config":{"name":"x","type":"ianaift:ethernetCsmacd"'
new "restconf GET device config json"
#expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config)" 0 "HTTP/$HVER 200" '"clixon-controller:config":{"interfaces":{"interface":\[{"name":"x","config":{"name":"x","type":"ianaift:ethernetCsmacd"'

new "restconf GET device config XML"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config)" 0 "HTTP/$HVER 200" '<config xmlns="http://clicon.org/controller"><interfaces xmlns="http://openconfig.net/yang/interfaces"><interface><name>x</name><config><name>x</name><type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type></config>'


# Across device mount-point
new "restconf GET across device mtpoint json"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/openconfig-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"openconfig-interfaces:interfaces":{"interface":\[{"name":"x","config":{"name":"x","type":"iana-if-type:ethernetCsmacd"' ''

new "restconf GET across device mtpoint xml"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/openconfig-interfaces:interfaces)" 0 "HTTP/$HVER 200" '<interfaces xmlns="http://openconfig.net/yang/interfaces"><interface><name>x</name><config><name>x</name><type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type></config>'

# 2. PUT
new "restconf PUT top"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/description -d '{"clixon-controller:description":"Test POST"}')" 0 "HTTP/$HVER 204"

if false; then # NYI
new "restconf PUT device"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/interfaces -d '{"openconfig-interfaces:interfaces"}' )" 0 "HTTP/$HVER 204"
new "restconf PUT across device mtpoint json"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/openconfig-interfaces:interfaces/interface=x/config -d '{"openconfig-interfaces:config":{"name":"x","type":"iana-if-type:ethernetCsmacd","description":"My description"}}')" 0 "HTTP/$HVER 204"

new "restconf PUT device XML"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/openconfig-interfaces:interfaces -d '<interfaces xmlns="http://openconfig.net/yang/interfaces"/>')" 0 "HTTP/$HVER 204"
fi
new "restconf PUT across device mtpoint XML"
expectpart "$(curl $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/config/openconfig-interfaces:interfaces/interface=x/config -d '<config xmlns="http://openconfig.net/yang/interfaces"><name>x</name><type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type><description>My description</description></config>')" 0 "HTTP/$HVER 204"

# 3. RPC/ connectivity
new "restconf GET connection OPEN"
Expand Down

0 comments on commit 0f8140a

Please sign in to comment.