Skip to content

Commit

Permalink
RESTCONF: device RPCs
Browse files Browse the repository at this point in the history
  • Loading branch information
olofhagsand committed Feb 27, 2025
1 parent 2f89834 commit 4a49546
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 57 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Expected: April 2025
* Controller RESTCONF support
* See: [Controller RESTCONF access not properly tested and documented](https://github.com/clicon/clixon-controller/issues/167)

### API changes on existing protocol/config features

* New `clixon-controller-config@2025-02-01.yang` revision
* Added restconf
* Added common fields to transaction state and notification

## 1.3.0
30 January 2025

Expand Down
2 changes: 1 addition & 1 deletion src/controller.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<CLICON_BACKEND_PIDFILE>@RUNSTATEDIR@/controller/controller.pid</CLICON_BACKEND_PIDFILE>
<!-- Start restconf daemon from backend -->
<CLICON_BACKEND_RESTCONF_PROCESS>true</CLICON_BACKEND_RESTCONF_PROCESS>
<!-- Disable duplicate check in NETCONF messages. Temporary pyapi issue-->
<!-- Disable duplicate check in NETCONF messages. pyapi issue: it is not yang aware-->
<CLICON_NETCONF_DUPLICATE_ALLOW>true</CLICON_NETCONF_DUPLICATE_ALLOW>
<!-- RESTCONF -->
<CLICON_RESTCONF_INSTALLDIR>@SBINDIR@</CLICON_RESTCONF_INSTALLDIR>
Expand Down
31 changes: 22 additions & 9 deletions src/controller_transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ controller_transaction_state_set(controller_transaction *ct,
return 0;
}

/*! Add device data from one device
/*! Copy XML to transaction devdata field
*
* Add a new device element and children of devdata
* see notification/device-data
Expand Down Expand Up @@ -330,7 +330,6 @@ controller_transaction_notify(clixon_handle h,
goto done;
cprintf(cb, "</reason>");
}
/* Check if any devdata, if so add and free */
if (ct->ct_devdata){
cprintf(cb, "<devices>");
if (clixon_xml2cbuf1(cb, ct->ct_devdata, 0, 0, NULL, -1, 1, 0) < 0)
Expand Down Expand Up @@ -586,10 +585,17 @@ controller_transaction_done(clixon_handle h,
/* This should be the only place */
if (controller_transaction_notify(h, ct) < 0)
goto done;
#if 0
/* This will leave devdata in the transaction history.
* Pros: You can check device rpc results via show state, not only immediate in the
* transaction-done notification
* Cons: Memory consumption, never freed, clutters history
*/
if (ct->ct_devdata){
xml_free(ct->ct_devdata); // XXX free at close?
ct->ct_devdata = NULL;
}
#endif
retval = 0;
done:
return retval;
Expand Down Expand Up @@ -851,23 +857,30 @@ controller_transaction_statedata(clixon_handle h,
do {
cprintf(cb, "<transaction>");
cprintf(cb, "<tid>%" PRIu64 "</tid>", ct->ct_id);
cprintf(cb, "<state>%s</state>", transaction_state_int2str(ct->ct_state));
if (ct->ct_description)
cprintf(cb, "<description>%s</description>", ct->ct_description);
if (ct->ct_origin)
cprintf(cb, "<origin>%s</origin>", ct->ct_origin);
if (ct->ct_state != TS_INIT)
cprintf(cb, "<result>%s</result>", transaction_result_int2str(ct->ct_result));
if (ct->ct_reason){
cprintf(cb, "<reason>");
xml_chardata_cbuf_append(cb, 0, ct->ct_reason);
cprintf(cb, "</reason>");
}
if (ct->ct_devdata){
cprintf(cb, "<devices>");
if (clixon_xml2cbuf1(cb, ct->ct_devdata, 0, 0, NULL, -1, 1, 0) < 0)
goto done;
cprintf(cb, "</devices>");
}
cprintf(cb, "<state>%s</state>", transaction_state_int2str(ct->ct_state));
if (ct->ct_description)
cprintf(cb, "<description>%s</description>", ct->ct_description);
if (ct->ct_origin)
cprintf(cb, "<origin>%s</origin>", ct->ct_origin);

if (ct->ct_warning){
cprintf(cb, "<warning>");
xml_chardata_cbuf_append(cb, 0, ct->ct_warning);
cprintf(cb, "</warning>");
}
if (ct->ct_state != TS_INIT)
cprintf(cb, "<result>%s</result>", transaction_result_int2str(ct->ct_result));
tv = &ct->ct_timestamp;
if (tv->tv_sec != 0){
char timestr[28];
Expand Down
41 changes: 37 additions & 4 deletions test/test-restconf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ if [ $? -ne 0 ]; then
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
# 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 @@ -234,7 +234,6 @@ new "restconf GET device config json"
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"' ''
Expand All @@ -257,7 +256,7 @@ new "restconf GET connection OPEN"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/conn-state)" 0 "HTTP/$HVER 200" '{"clixon-controller:conn-state":"OPEN"}'

new "restconf RPC connection close"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-controller:connection-change -d '{"clixon-controller:input":{"device":"*","operation":"CLOSE"}}')" 0 "HTTP/$HVER 200" 'Content-Type: application/yang-data+json' '{"clixon-controller:output":{"tid":'
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-controller:connection-change -d '{"clixon-controller:input":{"device":"*","operation":"CLOSE"}}')" 0 "HTTP/$HVER 200" 'Content-Type: application/yang-data+json' '{"clixon-controller:output":{"tid":"[0-9:.]*"}}'

new "restconf GET connection CLOSED"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/device=openconfig1/conn-state)" 0 "HTTP/$HVER 200" '{"clixon-controller:conn-state":"CLOSED"}'
Expand All @@ -274,8 +273,12 @@ PID=$!
new "Notification controller-transaction timeout:${TIMEOUT}s"
ret=$(curl $CURLOPTS -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/controller-transaction)

# echo "ret:$ret"
#echo "ret:$ret"

expect="data: <notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\"><eventTime>${DATE}T[0-9:.]*Z</eventTime><controller-transaction xmlns=\"http://clicon.org/controller\"><tid>[0-9:.]*</tid><result>SUCCESS</result></controller-transaction></notification>"

match=$(echo "$ret" | grep --null -Eo "$expect") || true

if [ -z "$match" ]; then
err "$expect" "$ret"
fi
Expand All @@ -298,6 +301,36 @@ expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+jso
new "restconf GET interface AA"
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" '<interface><name>AA</name><config><name>AA</name><type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type></config><state>'

# rpc template
new "Create RPC template"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/clixon-controller:devices -d '{"clixon-controller:rpc-template":[{"name":"stats","variables":{"variable":[{"name":"MODULES"}]},"config":{"clixon-lib:stats":{"modules":"${MODULES}"}}}]}')" 0 "HTTP/$HVER 201"

new "restconf GET rpc-template"
# XXX clixon-lib:stats not shown correctly
# This is because it is anydata and therefore not bound, and the JSON translation
# cannot map properly.
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:devices/rpc-template=stats)" 0 "HTTP/$HVER 200" '{"clixon-controller:rpc-template":\[{"name":"stats","variables":{"variable":\[{"name":"MODULES"}\]},"config":{"stats":{"modules":"${MODULES}"}}}\]}'

new "Apply template using RPC in background and save PID"
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-controller:device-template-apply -d '{"clixon-controller:input":{"type":"RPC","device":"openconfig*","template":"stats","variables":[{"variable":{"name":"MODULES","value":"true"}}]}}')" 0 "HTTP/$HVER 200" 'Content-Type: application/yang-data+json' '{"clixon-controller:output":{"tid":"[0-9:.]*"}}' &
PID=$!

new "Wait for notification timeout:${TIMEOUT}s"
ret=$(curl $CURLOPTS -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/controller-transaction)

#echo "ret:$ret"

expect="<controller-transaction xmlns=\"http://clicon.org/controller\"><tid>[0-9:.]*</tid><result>SUCCESS</result><devices><devdata xmlns=\"http://clicon.org/controller\"><name>openconfig1</name>"
match=$(echo "$ret" | grep --null -Eo "$expect") || true
if [ -z "$match" ]; then
err "$expect" "$ret"
fi

kill $PID 2> /dev/null

new "Get rpc transaction result"
expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+json" -X GET $RCPROTO://localhost/restconf/data/clixon-controller:transactions)" 0 "HTTP/$HVER 200" '{"clixon-controller:transactions":{"transaction":\[{"tid":"1","result":"SUCCESS"' # XXX devdata

if $RC; then
new "Kill restconf daemon"
expectpart "$($clixon_cli -1 -f $CFG -E $CFD processes restconf stop)" 0 ""
Expand Down
81 changes: 38 additions & 43 deletions yang/clixon-controller@2025-02-01.yang
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module clixon-controller {
revision 2025-02-01 {
description
"Added restconf
Added common fields to transaction state and notification
Released in 1.4.0";
}
revision 2024-11-01 {
Expand Down Expand Up @@ -454,6 +455,35 @@ module clixon-controller {
}
}
}
grouping transaction-common {
description "Common fields for transaction state and notification";
leaf tid{
description "Transaction id";
type uint64;
}
leaf result {
description "Transaction result";
type transaction-result;
}
leaf reason {
description "Reason for terminating transaction";
type string;
}
container devices {
list devdata {
description
"Replies from device, can be rpc reply for rpc-template transaction";
key name;
leaf name{
description "device name, should be one of /devices/device/name";
type string;
}
anydata data {
description "Device reply data";
}
}
}
}
container processes {
description "Process configuration";
container services {
Expand Down Expand Up @@ -634,34 +664,24 @@ module clixon-controller {
description
"Info about clixon controller device transaction.
A controller transaction spans commits on the controller as well as device actions,
such as pushing edits, validate, and commit of device configs.";
such as pushing edits, validate, and commit of device configs.
See also notification controller-transaction";
list transaction {
description "Transaction info";
key tid;
leaf tid{
description "Transaction id";
type uint64;
}
uses transaction-common;
leaf state {
description "Transaction state";
type transaction-state;
}
leaf result {
description "Transaction result";
type transaction-result;
}
leaf description {
description "Description of transacttion";
description "Description of transaction";
type string;
}
leaf origin {
description "Originator of error";
type string;
}
leaf reason {
description "Reason for terminating transaction";
type string;
}
leaf warning {
description "Warning, first encountered";
type string;
Expand Down Expand Up @@ -711,35 +731,10 @@ module clixon-controller {
}
}
notification controller-transaction {
description "A transaction has been completed.";
leaf tid {
type uint64;
description "Transaction id";
mandatory true;
}
leaf result {
type transaction-result;
description "Status at transaction termination";
mandatory true;
}
leaf reason {
description "Reason for terminating transaction (if ok=false)";
type string;
}
container devices {
list devdata {
description
"Replies from device, can be rpc reply for rpc-template transaction";
key name;
leaf name{
description "device name, should be one of /devices/device/name";
type string;
}
anydata data {
description "Device reply data";
}
}
}
description
"A transaction has been completed.
See also non-config transaction container";
uses transaction-common;
}
rpc config-pull {
description
Expand Down

0 comments on commit 4a49546

Please sign in to comment.