#!/bin/bash # Restconf basic functionality # Assume http server setup, such as nginx described in apps/restconf/README.md # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi APPNAME=example cfg=$dir/conf.xml # Use yang in example cat < $cfg $cfg /usr/local/share/clixon $IETFRFC clixon-example /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf false /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile 1 /usr/local/var/$APPNAME true EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there state='{"clixon-example:state": {"op": ["42","41","43"]}} ' new "test params: -f $cfg" if [ $BE -ne 0 ]; then new "kill old backend" sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -- -s" start_backend -s init -f $cfg -- -s fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" start_restconf -f $cfg new "waiting" wait_backend wait_restconf new "restconf tests" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" 0 " " new "restconf get restconf resource. RFC 8040 3.3 (json)" expecteq "$(curl -sG http://localhost/restconf)" 0 '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}} ' new "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" 0 '2016-06-21 ' # Should be alphabetically ordered new "restconf get restconf/operations. RFC8040 3.3.2 (json)" expecteq "$(curl -sG http://localhost/restconf/operations)" 0 '{"operations": {"clixon-example:client-rpc": null,"clixon-example:empty": null,"clixon-example:optional": null,"clixon-example:example": null,"clixon-lib:debug": null,"clixon-lib:ping": null,"ietf-netconf:get-config": null,"ietf-netconf:edit-config": null,"ietf-netconf:copy-config": null,"ietf-netconf:delete-config": null,"ietf-netconf:lock": null,"ietf-netconf:unlock": null,"ietf-netconf:get": null,"ietf-netconf:close-session": null,"ietf-netconf:kill-session": null,"ietf-netconf:commit": null,"ietf-netconf:discard-changes": null,"ietf-netconf:validate": null,"clixon-rfc5277:create-subscription": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" 0 '{"yang-library-version": "2016-06-21"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) expect="2016-06-21" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 '{"ietf-yang-library:module": [{"name": "ietf-interfaces","revision": "2018-02-20","namespace": "urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type": "implement"}]} ' new "restconf options. RFC 8040 4.1" expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" 0 "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" new "restconf head. RFC 8040 4.2" expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" expecteq "$(curl -s -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 "" new "restconf empty rpc with extra args (should fail)" expecteq "$(curl -s -X POST -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}} ' new "restconf get empty config + state json + module" expecteq "$(curl -sSG http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}} ' new "restconf get empty config + state json with wrong module name" expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No such yang module: badmodule"}}} ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state) expect='424143' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf get data/ json" expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op": ["42","41","43"]} ' 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/clixon-example:state/op=42) expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf get state operation eth0 type json" expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op": ["42","41","43"]} ' 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/clixon-example:state/op=42) expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf GET datastore" expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}} ' # Exact match new "restconf Add subtree to datastore using POST" expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","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 '"ietf-interfaces:interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]}' new "restconf delete interfaces" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "" new "restconf Check empty config" expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state " # XXX: gives # new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" # XXX cant get this to work #expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" 0 "" new "restconf Check eth/0/0 added config" expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new "restconf Check eth/0/0 added state" expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}} ' new "restconf Re-post eth/0/0 which should generate error" expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" new "Add nothing using POST" expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new "restconf Check description added" expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} ' new "restconf delete eth/0/0" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new "restconf Re-Delete eth/0/0 using none should generate error" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "" new "restconf get subtree" expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new "restconf rpc using POST json" expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output": {"x": "42","y": "42"}} ' new "restconf rpc using POST json wrong" expecteq "$(curl -s -X POST -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new "restconf rpc non-existing rpc without namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new "restconf rpc non-existing rpc" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new "restconf rpc missing input" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example) expect='4242' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf rpc using wrong prefix" expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" ret=$(curl -s -i -X POST -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc) expect='example' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new "restconf Add subtree without key (expected error)" expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "malformed key, expected '"'"'=restval'"'"'"}}} ' new "restconf Add subtree with too many keys (expected error)" expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "List key interface length mismatch"}}} ' new "Kill restconf daemon" stop_restconf if [ $BE -eq 0 ]; then exit # BE fi new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` if [ -z "$pid" ]; then err "backend already dead" fi # kill backend stop_backend -f $cfg rm -rf $dir