From 74fc0800ae146de05fd2fa3edefb9217b5b657d1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 22 Sep 2018 16:30:58 +0200 Subject: [PATCH] * Limited support of RFC 7895 YANG Module Library to list modules: * That is, limited support of: ietf-yang-library.yang * For example: `exampleietf-restconf-monitoring2017-01-26...` * Comply to RFC 8040 3.5.3.1 rule: api-identifier = [module-name ":"] identifier * The "module-name" was a no-op before. * This means that there was no difference between eg: GET /restconf/data/ietf-yang-library:modules-state and GET /restconf/data/XXXX:modules-state --- CHANGELOG.md | 13 +- apps/backend/backend_client.c | 170 +++++- apps/backend/backend_main.c | 4 + apps/backend/backend_plugin.c | 24 - apps/backend/clixon_backend_handle.h | 3 - apps/restconf/README.md | 12 +- apps/restconf/restconf_main.c | 14 +- lib/src/clixon_yang.c | 61 +- test/test_auth.sh | 2 +- test/test_auth_ext.sh | 3 +- test/test_event.sh | 21 +- test/test_list.sh | 4 +- test/test_restconf.sh | 37 +- test/test_restconf2.sh | 17 +- test/test_yang.sh | 5 +- yang/Makefile.in | 2 + yang/ietf-netconf-monitoring@2010-10-04.yang | 555 ++++++++++++++++++ .../ietf-netconf-notification@2008-07-01.yang | 94 +++ 18 files changed, 921 insertions(+), 120 deletions(-) create mode 100644 yang/ietf-netconf-monitoring@2010-10-04.yang create mode 100644 yang/ietf-netconf-notification@2008-07-01.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a46eab7..4d7ce430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,23 @@ ### Major New features ### API changes on existing features (you may need to change your code) -* Notification event streams enhancements +* Limited support of RFC 7895 YANG Module Library to list modules: + * That is, limited support of: ietf-yang-library.yang + * For example: `exampleietf-restconf-monitoring2017-01-26...` +* Notification event stream enhancements * Yang 1.1 notification support - * Event stream discovery support according to RFC 5277 Sec 3.2.5.1 (netconf) and RFC 8040 (restconf) + * Event stream discovery support according to RFC 5277 Sec 3.2.5.1 + * That is, support of ietf-restconf-monitoring.yang (mimics schema in 3.2.5.1) + * Event stream discovery support according to RFC 8040 (restconf) + * That is, support of ietf-netconf-notification.yang * clixon_restconf and clixon_netconf now take -D as command-line option instead of just -D * This aligns to clixon_cli and clixon_backend * Application command option -S to clixon_netconf is obsolete. Use `clixon_netconf -l s` instead. ### Minor changes +* Comply to RFC 8040 3.5.3.1 rule: api-identifier = [module-name ":"] identifier + * The "module-name" was a no-op before. + * This means that there was no difference between eg: GET /restconf/data/ietf-yang-library:modules-state and GET /restconf/data/XXXX:modules-state * Unified log handling for all clicon applications using -l e|o|s|f. * The options stand for e:stderr, o:stdout, s: syslog, f:file * Added file logging (`-l f` or `-l f`) for cases where neither syslog nor stderr is useful. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 71714e68..bede591f 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -261,6 +261,11 @@ from_client_get_config(clicon_handle h, return retval; } +/*! Get streams state according to RFC 5277 and RCC 8040 + * @retval -1 Error (fatal) + * @retval 0 OK + * @retval 1 Statedata callback failed + */ static int client_get_streams(clicon_handle h, char *xpath, @@ -289,9 +294,18 @@ client_get_streams(clicon_handle h, if (stream_get_xml(h, cb) < 0) goto done; cprintf(cb,""); - /* XXX. yspec */ - if (xml_parse_string(cbuf_get(cb), NULL, &x) < 0) + cprintf(cb,""); + if (stream_get_xml(h, cb) < 0) goto done; + cprintf(cb,""); + + /* XXX. yspec */ + if (xml_parse_string(cbuf_get(cb), NULL, &x) < 0){ + if (netconf_operation_failed_xml(xtop, "protocol", clicon_err_reason)< 0) + goto done; + retval = 1; + goto done; + } if (xml_merge(*xtop, x, yspec, &reason) < 0) goto done; if (reason){ @@ -299,11 +313,117 @@ client_get_streams(clicon_handle h, xml_purge(xc); if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0) goto done; - goto ok; + retval = 1; + goto done; } -#ifdef notyet + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (reason) + free(reason); + if (x) + xml_free(x); + return retval; +} + +/*! Get modules state according to RFC 7895 + * @retval -1 Error (fatal) + * @retval 0 OK + * @retval 1 Statedata callback failed + */ +static int +client_get_modules(clicon_handle h, + char *xpath, + cxobj **xtop) +{ + int retval = -1; + cxobj *x = NULL; + cxobj *xc; + char *reason = NULL; + yang_spec *yspec; + cbuf *cb; + yang_stmt *ymod = NULL; + yang_stmt *yrev; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (*xtop==NULL){ + clicon_err(OE_CFG, ENOENT, "XML tree expected"); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "clicon buffer"); + goto done; + } + cprintf(cb,""); + cprintf(cb,"1"); /* NYI */ + + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + cprintf(cb,""); + cprintf(cb,"%s", ymod->ys_argument); + if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL) + cprintf(cb,"%s", yrev->ys_argument); + else + cprintf(cb,""); + cprintf(cb,""); + } + cprintf(cb,""); + + /* XXX. yspec */ + if (xml_parse_string(cbuf_get(cb), NULL, &x) < 0){ + if (netconf_operation_failed_xml(xtop, "protocol", clicon_err_reason)< 0) + goto done; + retval = 1; + goto done; + } + if (xml_merge(*xtop, x, yspec, &reason) < 0) + goto done; + if (reason){ + while ((xc = xml_child_i(*xtop, 0)) != NULL) + xml_purge(xc); + if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0) + goto done; + retval = 1; + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (reason) + free(reason); + if (x) + xml_free(x); + return retval; +} + +/*! Get system state-data, including streams and plugins + * @retval -1 Error (fatal) + * @retval 0 OK + * @retval 1 Statedata callback failed + */ +static int +client_statedata(clicon_handle h, + char *xpath, + cxobj **xret) +{ + int retval = -1; + cxobj **xvec = NULL; + size_t xlen; + int i; + + if ((retval = client_get_streams(h, xpath, xret)) != 0) + goto done; + if ((retval = client_get_modules(h, xpath, xret)) != 0) + goto done; + if ((retval = clixon_plugin_statedata(h, xpath, xret)) != 0) + goto done; +#if 1 /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(*xtop, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* If vectors are specified then mark the nodes found and @@ -315,22 +435,17 @@ client_get_streams(clicon_handle h, xml_flag_set(xvec[i], XML_FLAG_MARK); } /* Remove everything that is not marked */ - if (!xml_flag(*xtop, XML_FLAG_MARK)) - if (xml_tree_prune_flagged_sub(*xtop, XML_FLAG_MARK, 1, NULL) < 0) + if (!xml_flag(*xret, XML_FLAG_MARK)) + if (xml_tree_prune_flagged_sub(*xret, XML_FLAG_MARK, 1, NULL) < 0) goto done; /* reset flag */ - if (xml_apply(*xtop, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; #endif - ok: retval = 0; done: - if (cb) - cbuf_free(cb); - if (reason) - free(reason); - if (x) - xml_free(x); + if (xvec) + free(xvec); return retval; } @@ -346,31 +461,26 @@ from_client_get(clicon_handle h, cxobj *xe, cbuf *cbret) { - int retval = -1; - cxobj *xfilter; - char *selector = "/"; - cxobj *xret = NULL; - int ret; - cbuf *cbx = NULL; /* Assist cbuf */ + int retval = -1; + cxobj *xfilter; + char *xpath = "/"; + cxobj *xret = NULL; + int ret; + cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) - if ((selector = xml_find_value(xfilter, "select"))==NULL) - selector="/"; + if ((xpath = xml_find_value(xfilter, "select"))==NULL) + xpath="/"; /* Get config */ - if (xmldb_get(h, "running", selector, 0, &xret) < 0){ + if (xmldb_get(h, "running", xpath, 0, &xret) < 0){ if (netconf_operation_failed(cbret, "application", "read registry")< 0) goto done; goto ok; } /* Get state data from plugins as defined by plugin_statedata(), if any */ assert(xret); - -#if 1 /* get netconf state data */ - if ((ret = client_get_streams(h, selector, &xret)) < 0) - goto done; -#endif clicon_err_reset(); - if ((ret = clixon_plugin_statedata(h, selector, &xret)) < 0) + if ((ret = client_statedata(h, xpath, &xret)) < 0) goto done; if (ret == 0){ /* OK */ cprintf(cbret, ""); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index fd128941..0873fc1c 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -775,6 +775,10 @@ main(int argc, goto done; if (yang_spec_append(h, CLIXON_DATADIR, "ietf-restconf-monitoring", NULL)< 0) goto done; + if (yang_spec_append(h, CLIXON_DATADIR, "ietf-netconf-notification", NULL)< 0) + goto done; + if (yang_spec_append(h, CLIXON_DATADIR, "ietf-yang-library", NULL)< 0) + goto done; /* Set options: database dir and yangspec (could be hidden in connect?)*/ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) goto done; diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index b11e9b67..255b0f1d 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -125,11 +125,8 @@ clixon_plugin_statedata(clicon_handle h, cxobj **xtop) { int retval = -1; - int i; cxobj *x = NULL; yang_spec *yspec; - cxobj **xvec = NULL; - size_t xlen; cxobj *xc; clixon_plugin *cp = NULL; plgstatedata_t *fn; /* Plugin statedata fn */ @@ -168,25 +165,6 @@ clixon_plugin_statedata(clicon_handle h, x = NULL; } } - /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(*xtop, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) - goto done; - - /* If vectors are specified then mark the nodes found and - * then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i #include #include @@ -615,7 +621,13 @@ main(int argc, /* Parse yang database spec file */ if (yang_spec_main(h) == NULL) goto done; - + /* Add system modules */ + if (yang_spec_append(h, CLIXON_DATADIR, "ietf-restconf-monitoring", NULL)< 0) + goto done; + if (yang_spec_append(h, CLIXON_DATADIR, "ietf-netconf-notification", NULL)< 0) + goto done; + if (yang_spec_append(h, CLIXON_DATADIR, "ietf-yang-library", NULL)< 0) + goto done; /* Call start function in all plugins before we go interactive Pass all args after the standard options to plugin_start */ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index acee080c..5b053217 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -620,33 +620,51 @@ yang_find_schemanode(yang_node *yn, */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *argument, + char *nodeid, yang_class class) { - yang_stmt *ys = NULL; - yang_stmt *yc = NULL; + yang_stmt *ymod = NULL; /* module */ + yang_stmt *yres = NULL; /* result */ + char *prefix = NULL; + char *id = NULL; int i; - for (i=0; iyp_len; i++){ - ys = ysp->yp_stmt[i]; - switch (class){ - case YC_NONE: - if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL) - return yc; - break; - case YC_DATANODE: - if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL) - return yc; - break; - case YC_SCHEMANODE: - if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL) - return yc; - break; - case YC_DATADEFINITION: - break; /* nyi */ + if (yang_nodeid_split(nodeid, &prefix, &id) < 0) + goto done; + if (prefix){ + if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL){ + if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) + goto ok; + goto done; } } - return NULL; + else /* No prefix given - loop through and find first */ + for (i=0; iyp_len; i++){ + ymod = ysp->yp_stmt[i]; + switch (class){ + case YC_NONE: + if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) + goto ok; + break; + case YC_DATANODE: + if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL) + goto ok; + break; + case YC_SCHEMANODE: + if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL) + goto ok; + break; + case YC_DATADEFINITION: + break; /* nyi */ + } + } + ok: + done: + if (prefix) + free(prefix); + if (id) + free(id); + return yres; } /*! Given a yang statement, find the prefix associated to this module @@ -822,7 +840,6 @@ yarg_prefix(yang_stmt *ys) return prefix; } - /*! Split yang node identifier into prefix and identifer. * @param[in] node-id * @param[out] prefix Malloced string. May be NULL. diff --git a/test/test_auth.sh b/test/test_auth.sh index 6837f8e8..d54609a8 100755 --- a/test/test_auth.sh +++ b/test/test_auth.sh @@ -148,7 +148,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)" '{"data": null} +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" 'null ' new "auth set authentication config" diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh index c1806891..55d57739 100755 --- a/test/test_auth_ext.sh +++ b/test/test_auth_ext.sh @@ -13,6 +13,7 @@ fyang=$dir/test.yang fyangerr=$dir/err.yang nacmfile=$dir/nacmfile +# Note filter out example_backend_nacm.so in CLICON_BACKEND_REGEXP below cat < $cfg $cfg @@ -190,7 +191,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)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +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}]}} ' new "Set x to 0" diff --git a/test/test_event.sh b/test/test_event.sh index 8bcd9c46..789dd793 100755 --- a/test/test_event.sh +++ b/test/test_event.sh @@ -67,7 +67,7 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -D 1 +sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err @@ -77,26 +77,21 @@ 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 +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 -new "restconf tests" - # get the stream list using netconf new "netconf event stream discovery RFC5277 Sec 3.2.5" -echo "$clixon_netconf -qf $cfg -y $fyang" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONFdefault NETCONF event streamfalseCLICONClicon logsfalse]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONFdefault NETCONF event streamfalseCLICONClicon logsfalse]]>]]>' -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONF' +new "netconf event stream discovery RFC8040 Sec 6.2" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONFdefault NETCONF event streamfalseCLICONClicon logsfalse]]>]]>' -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'NETCONF' +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},{ "name": "NETCONF","description": "default NETCONF event stream","replay-support": false}\]}}' -# get the stream list using restconf -new "restconf GET datastore" -#expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' - -new "netconf subscription" +#new "netconf subscription" #expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 new "Kill restconf daemon" diff --git a/test/test_list.sh b/test/test_list.sh index 25e2ee37..ed04b36a 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -5,7 +5,7 @@ APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh -exit 0 # NYI + cfg=$dir/conf_yang.xml fyang=$dir/test.yang @@ -91,6 +91,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$" +exit # NYI + new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c2fc32b1..c82948ac 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -135,6 +135,10 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +new "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"}]} + ' + 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" @@ -142,16 +146,23 @@ 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" -new2 "restconf empty rpc" +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)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +expecteq "$(curl -sSG http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 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 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"}}}} ' + new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) -expect="eth0ex:eth42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/interfaces-state) +expect="eth0ex:eth42" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -184,7 +195,7 @@ if [ -z "$match" ]; then fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +expecteq "$(curl -s -X GET http://localhost/restconf/data/interfaces-state)" '{"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}} ' # Exact match @@ -201,19 +212,23 @@ 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}\]}} ' -new2 "restconf delete interfaces" +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" 0 "$state" +expectfn "curl -sG http://localhost/restconf/data/interfaces-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 "" # 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)" "" -new2 "restconf Check eth/0/0 added" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +new2 "restconf Check eth/0/0 added config" +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} + ' + +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}]}} ' new2 "restconf Re-post eth/0/0 which should generate error" @@ -226,7 +241,7 @@ new "Add nothing using POST" expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} ' new "restconf delete eth/0/0" @@ -242,7 +257,7 @@ new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "ex:eth","if-index": 42}]}}} +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf rpc using POST json" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 1065cbee..6780b6d2 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -69,8 +69,8 @@ new "restconf tests" new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" -new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +new "restconf GET datastore intial" +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf GET interface" expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}' @@ -94,31 +94,31 @@ new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf DELETE whole datastore" expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": null}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' new "restconf PUT initial datastore" expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf PUT replace datastore" expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont2": {"name": "foo"}}}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont2" 0 '{"cont2": {"name": "foo"}}' new "restconf PUT initial datastore again" expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" @@ -126,9 +126,8 @@ expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type new "restconf PUT change interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 "" - new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data" 0 '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' new "restconf PUT add interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" diff --git a/test/test_yang.sh b/test/test_yang.sh index 3b59cfa7..94f56dc1 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -117,6 +117,9 @@ new "cli not defined extension" # This text yields an error, but the test cannot detect the error message yet #expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" 0 "Yang error: Extension ex:not-defined not found" +new "netconf schema resource, RFC 7895" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'ietf-yang-library2016-06-21' + new "netconf edit config" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$" @@ -140,7 +143,7 @@ new "netconf get leaf-list path" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one" new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" diff --git a/yang/Makefile.in b/yang/Makefile.in index d5c0c7ea..ae2a69b2 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -46,6 +46,8 @@ YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang +YANGSPECS += ietf-netconf-notification@2008-07-01.yang +YANGSPECS += ietf-yang-library@2016-06-21.yang APPNAME = clixon # subdir ehere these files are installed diff --git a/yang/ietf-netconf-monitoring@2010-10-04.yang b/yang/ietf-netconf-monitoring@2010-10-04.yang new file mode 100644 index 00000000..38a92d06 --- /dev/null +++ b/yang/ietf-netconf-monitoring@2010-10-04.yang @@ -0,0 +1,555 @@ +module ietf-netconf-monitoring { + + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"; + prefix "ncm"; + + import ietf-yang-types { prefix yang; } + import ietf-inet-types { prefix inet; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: Mehmet Ersue + + + WG Chair: Bert Wijnen + + + Editor: Mark Scott + + + Editor: Martin Bjorklund + "; + + description + "NETCONF Monitoring Module. + All elements in this module are read-only. + + Copyright (c) 2010 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's + Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6022; see + the RFC itself for full legal notices."; + + revision 2010-10-04 { + description + "Initial revision."; + reference + "RFC 6022: YANG Module for NETCONF Monitoring"; + } + + typedef netconf-datastore-type { + type enumeration { + enum running; + enum candidate; + enum startup; + } + description + "Enumeration of possible NETCONF datastore types."; + reference + "RFC 4741: NETCONF Configuration Protocol"; + } + + identity transport { + description + "Base identity for NETCONF transport types."; + } + + identity netconf-ssh { + base transport; + description + "NETCONF over Secure Shell (SSH)."; + reference + "RFC 4742: Using the NETCONF Configuration Protocol + over Secure SHell (SSH)"; + } + + identity netconf-soap-over-beep { + base transport; + description + "NETCONF over Simple Object Access Protocol (SOAP) over + Blocks Extensible Exchange Protocol (BEEP)."; + reference + "RFC 4743: Using NETCONF over the Simple Object + Access Protocol (SOAP)"; + } + + identity netconf-soap-over-https { + base transport; + description + "NETCONF over Simple Object Access Protocol (SOAP) + over Hypertext Transfer Protocol Secure (HTTPS)."; + reference + "RFC 4743: Using NETCONF over the Simple Object + Access Protocol (SOAP)"; + } + + identity netconf-beep { + base transport; + description + "NETCONF over Blocks Extensible Exchange Protocol (BEEP)."; + reference + "RFC 4744: Using the NETCONF Protocol over the + Blocks Extensible Exchange Protocol (BEEP)"; + } + + identity netconf-tls { + base transport; + description + "NETCONF over Transport Layer Security (TLS)."; + reference + "RFC 5539: NETCONF over Transport Layer Security (TLS)"; + } + + identity schema-format { + description + "Base identity for data model schema languages."; + } + + identity xsd { + base schema-format; + description + "W3C XML Schema Definition."; + reference + "W3C REC REC-xmlschema-1-20041028: + XML Schema Part 1: Structures"; + } + + identity yang { + base schema-format; + description + "The YANG data modeling language for NETCONF."; + reference + "RFC 6020: YANG - A Data Modeling Language for the + Network Configuration Protocol (NETCONF)"; + } + + identity yin { + base schema-format; + description + "The YIN syntax for YANG."; + reference + "RFC 6020: YANG - A Data Modeling Language for the + Network Configuration Protocol (NETCONF)"; + } + + identity rng { + base schema-format; + description + "Regular Language for XML Next Generation (RELAX NG)."; + reference + "ISO/IEC 19757-2:2008: RELAX NG"; + } + + identity rnc { + base schema-format; + description + "Relax NG Compact Syntax"; + reference + "ISO/IEC 19757-2:2008: RELAX NG"; + } + + grouping common-counters { + description + "Counters that exist both per session, and also globally, + accumulated from all sessions."; + + leaf in-rpcs { + type yang:zero-based-counter32; + description + "Number of correct messages received."; + } + leaf in-bad-rpcs { + type yang:zero-based-counter32; + description + "Number of messages received when an message was expected, + that were not correct messages. This includes XML parse + errors and errors on the rpc layer."; + } + leaf out-rpc-errors { + type yang:zero-based-counter32; + description + "Number of messages sent that contained an + element."; + } + leaf out-notifications { + type yang:zero-based-counter32; + description + "Number of messages sent."; + } + } + + container netconf-state { + config false; + description + "The netconf-state container is the root of the monitoring + data model."; + + container capabilities { + description + "Contains the list of NETCONF capabilities supported by the + server."; + + leaf-list capability { + type inet:uri; + description + "List of NETCONF capabilities supported by the server."; + } + } + + container datastores { + description + "Contains the list of NETCONF configuration datastores."; + + list datastore { + key name; + description + "List of NETCONF configuration datastores supported by + the NETCONF server and related information."; + + leaf name { + type netconf-datastore-type; + description + "Name of the datastore associated with this list entry."; + } + container locks { + presence + "This container is present only if the datastore + is locked."; + description + "The NETCONF and operations allow + a client to lock specific resources in a datastore. The + NETCONF server will prevent changes to the locked + resources by all sessions except the one that acquired + the lock(s). + + Monitoring information is provided for each datastore + entry including details such as the session that acquired + the lock, the type of lock (global or partial) and the + list of locked resources. Multiple locks per datastore + are supported."; + + grouping lock-info { + description + "Lock related parameters, common to both global and + partial locks."; + + leaf locked-by-session { + type uint32; + mandatory true; + description + "The session ID of the session that has locked + this resource. Both a global lock and a partial + lock MUST contain the NETCONF session-id. + + If the lock is held by a session that is not managed + by the NETCONF server (e.g., a CLI session), a session + id of 0 (zero) is reported."; + reference + "RFC 4741: NETCONF Configuration Protocol"; + } + leaf locked-time { + type yang:date-and-time; + mandatory true; + description + "The date and time of when the resource was + locked."; + } + } + choice lock-type { + description + "Indicates if a global lock or a set of partial locks + are set."; + + container global-lock { + description + "Present if the global lock is set."; + uses lock-info; + } + + list partial-lock { + key lock-id; + description + "List of partial locks."; + reference + "RFC 5717: Partial Lock Remote Procedure Call (RPC) for + NETCONF"; + + leaf lock-id { + type uint32; + description + "This is the lock id returned in the + response."; + } + uses lock-info; + leaf-list select { + type yang:xpath1.0; + min-elements 1; + description + "The xpath expression that was used to request + the lock. The select expression indicates the + original intended scope of the lock."; + } + leaf-list locked-node { + type instance-identifier; + description + "The list of instance-identifiers (i.e., the + locked nodes). + + The scope of the partial lock is defined by the list + of locked nodes."; + } + } + } + } + } + } + container schemas { + description + "Contains the list of data model schemas supported by the + server."; + + list schema { + key "identifier version format"; + + description + "List of data model schemas supported by the server."; + + leaf identifier { + type string; + description + "Identifier to uniquely reference the schema. The + identifier is used in the operation and may + be used for other purposes such as file retrieval. + + For modeling languages that support or require a data + model name (e.g., YANG module name) the identifier MUST + match that name. For YANG data models, the identifier is + the name of the module or submodule. In other cases, an + identifier such as a filename MAY be used instead."; + } + leaf version { + type string; + description + "Version of the schema supported. Multiple versions MAY be + supported simultaneously by a NETCONF server. Each + version MUST be reported individually in the schema list, + i.e., with same identifier, possibly different location, + but different version. + + For YANG data models, version is the value of the most + recent YANG 'revision' statement in the module or + submodule, or the empty string if no 'revision' statement + is present."; + } + leaf format { + type identityref { + base schema-format; + } + description + "The data modeling language the schema is written + in (currently xsd, yang, yin, rng, or rnc). + For YANG data models, 'yang' format MUST be supported and + 'yin' format MAY also be provided."; + } + leaf namespace { + type inet:uri; + mandatory true; + description + "The XML namespace defined by the data model. + + For YANG data models, this is the module's namespace. + If the list entry describes a submodule, this field + contains the namespace of the module to which the + submodule belongs."; + } + leaf-list location { + type union { + type enumeration { + enum "NETCONF"; + } + type inet:uri; + } + description + "One or more locations from which the schema can be + retrieved. This list SHOULD contain at least one + entry per schema. + + A schema entry may be located on a remote file system + (e.g., reference to file system for ftp retrieval) or + retrieved directly from a server supporting the + operation (denoted by the value 'NETCONF')."; + } + } + } + container sessions { + description + "The sessions container includes session-specific data for + NETCONF management sessions. The session list MUST include + all currently active NETCONF sessions."; + + list session { + key session-id; + description + "All NETCONF sessions managed by the NETCONF server + MUST be reported in this list."; + + leaf session-id { + type uint32 { + range "1..max"; + } + description + "Unique identifier for the session. This value is the + NETCONF session identifier, as defined in RFC 4741."; + reference + "RFC 4741: NETCONF Configuration Protocol"; + } + leaf transport { + type identityref { + base transport; + } + mandatory true; + description + "Identifies the transport for each session, e.g., + 'netconf-ssh', 'netconf-soap', etc."; + } + leaf username { + type string; + mandatory true; + description + "The username is the client identity that was authenticated + by the NETCONF transport protocol. The algorithm used to + derive the username is NETCONF transport protocol specific + and in addition specific to the authentication mechanism + used by the NETCONF transport protocol."; + } + leaf source-host { + type inet:host; + description + "Host identifier of the NETCONF client. The value + returned is implementation specific (e.g., hostname, + IPv4 address, IPv6 address)"; + } + leaf login-time { + type yang:date-and-time; + mandatory true; + description + "Time at the server at which the session was established."; + } + uses common-counters { + description + "Per-session counters. Zero based with following reset + behaviour: + - at start of a session + - when max value is reached"; + } + } + } + + container statistics { + description + "Statistical data pertaining to the NETCONF server."; + + leaf netconf-start-time { + type yang:date-and-time; + description + "Date and time at which the management subsystem was + started."; + } + leaf in-bad-hellos { + type yang:zero-based-counter32; + description + "Number of sessions silently dropped because an + invalid message was received. This includes + messages with a 'session-id' attribute, bad namespace, and + bad capability declarations."; + } + leaf in-sessions { + type yang:zero-based-counter32; + description + "Number of sessions started. This counter is incremented + when a message with a is sent. + + 'in-sessions' - 'in-bad-hellos' = + 'number of correctly started netconf sessions'"; + } + leaf dropped-sessions { + type yang:zero-based-counter32; + description + "Number of sessions that were abnormally terminated, e.g., + due to idle timeout or transport close. This counter is not + incremented when a session is properly closed by a + operation, or killed by a + operation."; + } + uses common-counters { + description + "Global counters, accumulated from all sessions. + Zero based with following reset behaviour: + - re-initialization of NETCONF server + - when max value is reached"; + } + } + } + + rpc get-schema { + description + "This operation is used to retrieve a schema from the + NETCONF server. + + Positive Response: + The NETCONF server returns the requested schema. + + Negative Response: + If requested schema does not exist, the is + 'invalid-value'. + + If more than one schema matches the requested parameters, the + is 'operation-failed', and is + 'data-not-unique'."; + + input { + leaf identifier { + type string; + mandatory true; + description + "Identifier for the schema list entry."; + } + leaf version { + type string; + description + "Version of the schema requested. If this parameter is not + present, and more than one version of the schema exists on + the server, a 'data-not-unique' error is returned, as + described above."; + } + leaf format { + type identityref { + base schema-format; + } + description + "The data modeling language of the schema. If this + parameter is not present, and more than one formats of + the schema exists on the server, a 'data-not-unique' error + is returned, as described above."; + } + } + output { + anyxml data { + description + "Contains the schema content."; + } + } + } +} diff --git a/yang/ietf-netconf-notification@2008-07-01.yang b/yang/ietf-netconf-notification@2008-07-01.yang new file mode 100644 index 00000000..1e98e455 --- /dev/null +++ b/yang/ietf-netconf-notification@2008-07-01.yang @@ -0,0 +1,94 @@ +module ietf-restconf-monitoring { + namespace "urn:ietf:params:xml:ns:netconf:notification:1:0"; + prefix "rcmon"; + + import ietf-yang-types { prefix yang; } + import ietf-inet-types { prefix inet; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + description + "Note this is a translation from RFC 5277 schema in section 4 to Yang. + RFC 5277 is Copyright (C) The IETF Trust (2008)."; + + revision 2008-07-01 { + description + "Initial revision."; + reference + "RFC 5277: NETCONF Event Notifications."; + } + + container netconf { + config false; + description + "Contains NETCONF protocol monitoring information."; + + container capabilities { + description + "Contains a list of protocol capability URIs."; + + leaf-list capability { + type inet:uri; + description + "A RESTCONF protocol capability URI."; + } + } + + container streams { + description + "Container representing the notification event streams + supported by the server."; + reference + "RFC 5277, Section 3.4, element."; + list stream { + key name; + description + "Each entry describes an event stream supported by + the server."; + + leaf name { + type string; + description + "The stream name."; + reference + "RFC 5277, Section 3.4, element."; + } + + leaf description { + type string; + description + "Description of stream content."; + reference + "RFC 5277, Section 3.4, element."; + } + + leaf replay-support { + type boolean; + default false; + description + "Indicates if replay buffer is supported for this stream. + If 'true', then the server MUST support the 'start-time' + and 'stop-time' query parameters for this stream."; + reference + "RFC 5277, Section 3.4, element."; + } + + leaf replay-log-creation-time { + when "../replay-support" { + description + "Only present if notification replay is supported."; + } + type yang:date-and-time; + description + "Indicates the time the replay log for this stream + was created."; + reference + "RFC 5277, Section 3.4, + element."; + } + + } + } + } +}