* Major rewrite of event streams

* If you used old event callbacks API, you need to switch to the streams API
    * See clixon_stream.[ch]
  * Old streams API which needs to be removed include:
    * clicon_log_register_callback()
    * subscription_add() --> stream_register()
    * backend_notify() and backend_notify_xml() - use stream_notify() instead
* Example uses "NETCONF" stream instead of "ROUTING"
* Added timeout option -t for clixon_netconf - quit after max time.
This commit is contained in:
Olof hagsand 2018-09-30 14:51:30 +02:00
parent d7fbe75c9e
commit 98f3cd0e32
31 changed files with 597 additions and 635 deletions

View file

@ -2,6 +2,8 @@
# Define test functions.
# Create working dir as variable "dir"
#set -e
testnr=0
testname=
@ -32,6 +34,7 @@ err(){
echo -e "\e[31m\nError in Test$testnr [$testname]:"
if [ $# -gt 0 ]; then
echo "Expected: $1"
echo
fi
if [ $# -gt 1 ]; then
echo "Received: $2"
@ -172,15 +175,27 @@ expectwait(){
input=$2
expect=$3
wait=$4
# Do while read stuff
sleep 10|cat <(echo $input) -| $cmd | while [ 1 ] ; do
read ret
echo timeout > /tmp/flag
ret=""
sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do
read r
# echo "r:$r"
ret="$ret$r"
match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then
echo error > /tmp/flag
err $expect "$ret"
else
echo ok > /tmp/flag # only this is OK
break;
fi
break
done
cat /tmp/flag
if [ $(cat /tmp/flag) != "ok" ]; then
cat /tmp/flag
exit
fi
}

View file

@ -39,12 +39,6 @@ EOF
cat <<EOF > $fyang
module $APPNAME{
prefix ex;
import ietf-interfaces {
prefix if;
}
import iana-if-type {
prefix ianaift;
}
container authentication {
description "Example code for enabling www basic auth and some example
users";
@ -70,21 +64,13 @@ module $APPNAME{
type int32;
description "something to edit";
}
container interfaces-state {
container state {
config false;
list interface{
key "name";
leaf name{
type string;
}
leaf type{
type string;
}
leaf if-index {
type int32;
}
description "state data for example application";
leaf-list op {
type string;
}
}
}
}
EOF
@ -191,7 +177,7 @@ new "restconf DELETE whole datastore"
expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" ""
new2 "auth get"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/ietf-interfaces:interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
new "Set x to 0"

View file

@ -16,6 +16,8 @@ cat <<EOF > $cfg
<CLICON_YANG_MODULE_MAIN>$fyang</CLICON_YANG_MODULE_MAIN>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
@ -28,12 +30,12 @@ cat <<EOF > $cfg
</config>
EOF
# RFC5277 NETCONF Event Notifications
# RFC5277 NETCONF Event Notifications
# using reportingEntity (rfc5277) not reporting-entity (rfc8040)
cat <<EOF > $fyang
module example {
namespace "http://example.com/event/1.0";
prefix ex;
organization "Example, Inc.";
contact "support at example.com";
description "Example Notification Data Model Module.";
@ -41,14 +43,13 @@ cat <<EOF > $fyang
description "Initial version.";
reference "example.com document 2-9976.";
}
notification event {
description "Example notification event.";
leaf event-class {
type string;
description "Event class identifier.";
}
container reporting-entity {
container reportingEntity {
description "Event specific information.";
leaf card {
type string;
@ -60,7 +61,14 @@ cat <<EOF > $fyang
description "Event severity description.";
}
}
}
container state {
config false;
description "state data for example application";
leaf-list op {
type string;
}
}
}
EOF
# kill old backend (if any)
@ -78,30 +86,35 @@ fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D 1
sleep 1
# get the stream list using netconf
new "netconf event stream discovery RFC5277 Sec 3.2.5"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="netconf/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><netconf><streams><stream><name>CLICON</name><description>Clicon logs</description><replay-support>false</replay-support></stream><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support></stream></streams></netconf></data></rpc-reply>]]>]]>'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="netconf/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><netconf><streams><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support></stream></streams></netconf></data></rpc-reply>]]>]]>'
new "netconf event stream discovery RFC8040 Sec 6.2"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="restconf-state/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><restconf-state><streams><stream><name>CLICON</name><description>Clicon logs</description><replay-support>false</replay-support><access><encoding>xml</encoding><location>https://example.com/stream/CLICON</location></access></stream><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support><access><encoding>xml</encoding><location>https://example.com/stream/NETCONF</location></access></stream></streams></restconf-state></data></rpc-reply>]]>]]>'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get><filter type="xpath" select="restconf-state/streams" xmlns="urn:ietf:params:xml:ns:netmod:notification"/></get></rpc>]]>]]>' '<rpc-reply><data><restconf-state><streams><stream><name>NETCONF</name><description>default NETCONF event stream</description><replay-support>false</replay-support><access><encoding>xml</encoding><location>/stream/NETCONF</location></access></stream></streams></restconf-state></data></rpc-reply>]]>]]>'
new "restconf event stream discovery RFC8040 Sec 6.2"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "CLICON","description": "Clicon logs","replay-support": false,"access": \[{"encoding": "xml","location": "https://example.com/stream/CLICON"}\]},{ "name": "NETCONF","description": "default NETCONF event stream","replay-support": false,"access": \[{"encoding": "xml","location": "https://example.com/stream/NETCONF"}\]}\]}'
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "NETCONF","description": "default NETCONF event stream","replay-support": false,"access": \[{"encoding": "xml","location": "/stream/NETCONF"}\]}\]}'
new "restconf subscribe RFC8040 Sec 6.3, get location"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=NETCONF/access=xml/location" 0 '{"location": "https://example.com/stream/NETCONF"}'
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=NETCONF/access=xml/location" 0 '{"location": "/stream/NETCONF"}'
new "restconf monitor event stream RFC8040 Sec 6.3"
#expectfn "curl -s -X GET http://localhost/stream/NETCONF" 0 ''
new "netconf NETCONF subscription"
expectwait "$clixon_netconf -qf $cfg -y $fyang" '<rpc><create-subscription><stream>NETCONF</stream></create-subscription></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
#new "netconf subscription" NOTYET
#expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><notification><event>Routing notification</event></notification>]]>]]>$" 30
new "netconf NETCONF subscription with simple filter"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>NETCONF</stream><filter type=\"xpath\" select=\"event\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
new "netconf NETCONF subscription with filter classfier"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>NETCONF</stream><filter type=\"xpath\" select=\"event[event-class='fault']\"/></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>20' 5
#new "restconf monitor event stream RFC8040 Sec 6.3"
#XXX expectfn "curl -s -X GET http://localhost/stream/NETCONF" 0 ''
new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf
@ -119,4 +132,4 @@ if [ -n "$pid" ]; then
sudo kill $pid
fi
rm -rf $dir
#rm -rf $dir

View file

@ -65,6 +65,13 @@ module example{
}
}
}
container state {
config false;
description "state data for example application";
leaf-list op {
type string;
}
}
}
EOF
@ -180,10 +187,10 @@ new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit state operation should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><interfaces-state><interface><name>eth1</name><type>ex:eth</type></interface></interfaces-state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value</error-tag>"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><state><op>42</op></state></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>invalid-value</error-tag>"
new "netconf get state operation"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get><filter type=\"xpath\" select=\"/interfaces-state\"/></get></rpc>]]>]]>" "^<rpc-reply><data><interfaces-state><interface><name>eth0</name><type>ex:eth</type><if-index>42</if-index></interface></interfaces-state></data></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get><filter type=\"xpath\" select=\"/state\"/></get></rpc>]]>]]>" "^<rpc-reply><data><state><op>42</op></state></data></rpc-reply>]]>]]>$"
new "netconf lock/unlock"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]><rpc><unlock><target><candidate/></target></unlock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -225,18 +232,19 @@ new "netconf client-side rpc"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><ex:client-rpc><request>example</request></ex:client-rpc></rpc>]]>]]>" "^<rpc-reply><result>ok</result></rpc-reply>]]>]]>$"
new "netconf subscription"
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><notification><event>Routing notification</event></notification>]]>]]>$" 30
expectwait "$clixon_netconf -qf $cfg -y $fyang" "<rpc><create-subscription><stream>NETCONF</stream></create-subscription></rpc>]]>]]>" '^<rpc-reply><ok/></rpc-reply>]]>]]><notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>201' 10
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
# Check if still alive
pid=`pgrep clixon_backend`
if [ -n "$pid" ]; then
sudo kill $pid
fi
rm -rf $dir

View file

@ -71,11 +71,18 @@ module example{
}
}
}
container state {
config false;
description "state data for example application";
leaf-list op {
type string;
}
}
}
EOF
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
state='{"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}}'
state='{"state": {"op": "42"}}'
# kill old backend (if any)
new "kill old backend"
@ -136,7 +143,7 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895"
new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895"
expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing"}]}
'
@ -151,52 +158,52 @@ new "restconf empty rpc"
expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" ""
new2 "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}
expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
new2 "restconf get empty config + state json with module name"
expecteq "$(curl -sSG http://localhost/restconf/data/ietf-interfaces:interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}
new2 "restconf get empty config + state json + module"
expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": {"op": "42"}}
'
new2 "restconf get empty config + state json with wrong module name"
expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:interfaces-state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:interfaces-state"}}}} '
expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:state"}}}} '
new "restconf get empty config + state xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state)
expect="<interfaces-state><interface><name>eth0</name><type>ex:eth</type><if-index>42</if-index></interface></interfaces-state>"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state)
expect="<state><op>42</op></state>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new2 "restconf get data/interfaces-state/interface=eth0 json"
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}
new2 "restconf get data/ json"
expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"}
'
new "restconf get state operation eth0 xml"
# Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0)
expect="<interface><name>eth0</name><type>ex:eth</type><if-index>42</if-index></interface>"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42)
expect="<op>42</op>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new2 "restconf get state operation eth0 type json"
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "ex:eth"}
expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"}
'
new "restconf get state operation eth0 type xml"
# Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)
expect="<type>ex:eth</type>"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42)
expect="<op>42</op>"
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new2 "restconf GET datastore"
expecteq "$(curl -s -X GET http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}
expecteq "$(curl -s -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
# Exact match
@ -210,14 +217,14 @@ expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type"
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "ex:eth","if-index": 42}\]}}
expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}}
'
new "restconf delete interfaces"
expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
new "restconf Check empty config"
expectfn "curl -sG http://localhost/restconf/data/interfaces-state" 0 "$state"
expectfn "curl -sG http://localhost/restconf/data/state" 0 "$state"
new "restconf Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 ""
@ -229,7 +236,7 @@ expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces
'
new2 "restconf Check eth/0/0 added state"
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}
expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
new2 "restconf Re-post eth/0/0 which should generate error"