From e56cf607a3744f328e11b8906bac6563c19f7da5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 20 Jul 2017 10:54:31 +0200 Subject: [PATCH] Added YANG RPC support, with example rpc documentation and testcase (test7.sh); Extended example with ietf-routing (not only ietf-ip). --- CHANGELOG.md | 4 +++ README.md | 2 +- apps/netconf/netconf_plugin.c | 53 +++++++++++++++++++++++++++------- example/Makefile.in | 1 + example/README.md | 43 +++++++++++++++++++++------- example/example.yang | 10 +++++++ example/routing.conf.local | 5 ++-- example/routing_backend.c | 36 +++++++++++++++++------ example/routing_cli.c | 54 ++++++++++++++++------------------- example/routing_cli.cli | 10 +++---- lib/clixon/clixon_yang.h | 5 ++-- lib/src/clixon_yang.c | 24 ++++++++++------ lib/src/clixon_yang_type.c | 2 -- test/test1.sh | 2 +- test/test4.sh | 2 +- test/test6.sh | 34 +++------------------- test/test7.sh | 2 +- 17 files changed, 177 insertions(+), 112 deletions(-) create mode 100644 example/example.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 1556a53e..ac27dac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Clixon CHANGELOG +- Extended ietf-ip example with ietf-routing. + +- Added YANG RPC support, with example rpc documentation and testcase (test7.sh). + - Added completion for generated cli leafrefs for both absolute and relatve paths. - Added validation for leafref forward and backward references. diff --git a/README.md b/README.md index 55d380fa..adb627dd 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented w - object-references - if-feature - unique -- rpc + diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c index 474da891..c1ff19e1 100644 --- a/apps/netconf/netconf_plugin.c +++ b/apps/netconf/netconf_plugin.c @@ -196,26 +196,28 @@ catch: return -1; } +/*! Struct to carry info into and out of ys_find_rpc callback + */ +typedef struct { + char *name; /* name of rpc */ + yang_stmt *yrpc; /* matching yang statement */ +} find_rpc_arg; + +/*! Check yang rpc statement, return yang rpc statement if found + */ static int ys_find_rpc(yang_stmt *ys, void *arg) { - cxobj *xn = (cxobj*)arg; - char *name = xml_name(xn); + find_rpc_arg *fra = (find_rpc_arg*)arg; - if (ys->ys_keyword == Y_RPC && strcmp(name, ys->ys_argument) == 0){ - /* - * XXX - * 1. Check xn arguments with input statement. - * 2. Send to backend as clicon_msg-encode() - * 3. In backend to similar but there call actual backend - */ + if (strcmp(fra->name, ys->ys_argument) == 0){ + fra->yrpc = ys; return 1; /* handled */ } return 0; } - /*! See if there is any callback registered for this tag * * @param[in] h clicon handle @@ -234,6 +236,10 @@ netconf_plugin_callbacks(clicon_handle h, int retval = -1; netconf_reg_t *nreg; yang_spec *yspec; + yang_stmt *yrpc; + yang_stmt *yinput; + find_rpc_arg fra = {0,0}; + int ret; if (deps != NULL){ nreg = deps; @@ -249,8 +255,33 @@ netconf_plugin_callbacks(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (yang_apply((yang_node*)yspec, ys_find_rpc, xn) < 0) + /* Find yang rpc statement, return yang rpc statement if found */ + fra.name = xml_name(xn); + if ((ret = yang_apply((yang_node*)yspec, Y_RPC, ys_find_rpc, &fra)) < 0) goto done; + /* Check if found */ + if (ret == 1){ + yrpc = fra.yrpc; + if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ + xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ + if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) + goto done; + if (xml_apply(xn, CX_ELMNT, + (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + goto done; + if (xml_yang_validate_add(xn, NULL) < 0) + goto done; + } + /* + * 1. Check xn arguments with input statement. + * 2. Send to backend as clicon_msg-encode() + * 3. In backend to similar but there call actual backend + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + retval = 1; /* handled by callback */ + goto done; + } retval = 0; done: return retval; diff --git a/example/Makefile.in b/example/Makefile.in index 0ebdc3ad..6c94b660 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -67,6 +67,7 @@ YANGSPECS += ietf-routing@2014-10-26.yang YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipsec@2016-03-09.yang +YANGSPECS += example.yang # Backend plugin BE_SRC = routing_backend.c diff --git a/example/README.md b/example/README.md index b833feee..86806df6 100644 --- a/example/README.md +++ b/example/README.md @@ -65,22 +65,45 @@ Routing notification ... ``` -## Extending +## Operation data -Clixon has an extension mechanism which can be used to make extended internal -netconf messages to the backend configuration engine. You may need this to -make some special operation that is not covered by standard -netconf functions. The example has a simple "echo" downcall -mechanism that simply echoes what is sent down and is included for -reference. A more realistic downcall would perform some action, such as -reading some status. +Clixon implements Yang RPC operations by an extension mechanism. The +extension mechanism enables you to add application-specific +operations. It works by adding user-defined callbacks for added +netconf operations. It is possible to use the extension mechanism +independent of the yang rpc construct, but it is recommended to use +that, and the example includes such an example: Example: ``` -cli> downcall "This is a string" -This is a string +cli> rpc ipv4 + + + ``` +The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function). +``` + + + ipv4 + + +``` + +The backend in turn registers a callback (fib_route()) which handles the RPC. +``` +static int +fib_route(clicon_handle h, + cxobj *xe, /* Request: */ + struct client_entry *ce, /* Client session */ + cbuf *cbret, /* Reply eg ... */ + void *arg) /* Argument given at register */ +{ + cprintf(cbret, ""); + return 0; +} +``` ## State data Netconf and restconf GET also returns state data, in contrast to diff --git a/example/example.yang b/example/example.yang new file mode 100644 index 00000000..3c6a6f3a --- /dev/null +++ b/example/example.yang @@ -0,0 +1,10 @@ +module example { + import ietf-ip { + prefix ip; + } + import ietf-routing { + prefix rt; + } + description + "Example code that includes ietf-ip and ietf-routing"; +} diff --git a/example/routing.conf.local b/example/routing.conf.local index 698e56f4..4427abcf 100644 --- a/example/routing.conf.local +++ b/example/routing.conf.local @@ -5,11 +5,12 @@ CLICON_CLI_MODE routing # Option used to construct initial yang file: # [@] -CLICON_YANG_MODULE_MAIN ietf-ip +#CLICON_YANG_MODULE_MAIN ietf-ip +CLICON_YANG_MODULE_MAIN example # Option used to construct initial yang file: # [@] -CLICON_YANG_MODULE_REVISION 2014-06-16 +#CLICON_YANG_MODULE_REVISION 2014-06-16 # Generate code for CLI completion of existing db symbols # CLICON_CLI_GENMODEL_COMPLETION 0 diff --git a/example/routing_backend.c b/example/routing_backend.c index 33a86c11..e65bbd0d 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -119,14 +119,27 @@ notification_timer_setup(clicon_handle h) return event_reg_timeout(t, notification_timer, h, "notification timer"); } +/*! IETF Routing fib-route rpc */ static int -routing_downcall(clicon_handle h, - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ +fib_route(clicon_handle h, + cxobj *xe, /* Request: */ + struct client_entry *ce, /* Client session */ + cbuf *cbret, /* Reply eg ... */ + void *arg) /* Argument given at register */ { - cprintf(cbret, "%s", xml_body(xe)); + cprintf(cbret, ""); + return 0; +} + +/*! IETF Routing route-count rpc */ +static int +route_count(clicon_handle h, + cxobj *xe, /* Request: */ + struct client_entry *ce, /* Client session */ + cbuf *cbret, /* Reply eg ... */ + void *arg) /* Argument given at register */ +{ + cprintf(cbret, ""); return 0; } @@ -173,10 +186,15 @@ plugin_init(clicon_handle h) if (notification_timer_setup(h) < 0) goto done; - /* Register callback for netconf application-specific rpc call */ - if (backend_rpc_cb_register(h, routing_downcall, + /* Register callback for routing rpc calls */ + if (backend_rpc_cb_register(h, fib_route, NULL, - "myrouting"/* Xml tag when callback is made */ + "fib-route"/* Xml tag when callback is made */ + ) < 0) + goto done; + if (backend_rpc_cb_register(h, route_count, + NULL, + "route-count"/* Xml tag when callback is made */ ) < 0) goto done; retval = 0; diff --git a/example/routing_cli.c b/example/routing_cli.c index e2e67b2b..38bf2125 100644 --- a/example/routing_cli.c +++ b/example/routing_cli.c @@ -92,40 +92,36 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) return retval; } -/*! get argument and send as string to backend as RPC (which returns the string) - */ +/*! Example "downcall": ietf-routing fib-route RPC */ int -downcall(clicon_handle h, - cvec *vars, - cvec *argv) +fib_route_rpc(clicon_handle h, + cvec *cvv, + cvec *argv) { - int retval = -1; - struct clicon_msg *msg = NULL; - char *str=""; - cg_var *cv; - cxobj *xret=NULL; - cxobj *xerr; - cxobj *xdata; + int retval = -1; + cg_var *instance; + cxobj *xtop = NULL; + cxobj *xrpc; + cxobj *xret = NULL; - if (cvec_len(vars)==2){ - if ((cv = cvec_i(vars, 1)) != NULL) - str = cv_string_get(cv); - } - if ((msg = clicon_msg_encode("%s", str)) == NULL) + /* User supplied variable in CLI command */ + instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ + /* Create XML for fib-route netconf RPC */ + if (clicon_xml_parse(&xtop, "%s", instance) < 0) goto done; - if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) + /* Skip top-level */ + xrpc = xml_child_i(xtop, 0); + /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - clicon_rpc_generate_error(xerr); - goto done; - } - if ((xdata = xpath_first(xret, "//ok")) != NULL) - cli_output(stdout, "%s\n", xml_body(xdata)); - retval = 0; - done: + /* Print result */ + xml_print(stdout, xml_child_i(xret, 0)); + retval = 0; + done: if (xret) - xml_free(xret); - if (msg) - free(msg); + xml_free(xret); + if (xtop) + xml_free(xtop); return retval; } + diff --git a/example/routing_cli.cli b/example/routing_cli.cli index 78899c77..2edacd01 100644 --- a/example/routing_cli.cli +++ b/example/routing_cli.cli @@ -4,10 +4,10 @@ CLICON_PROMPT="%U@%H> "; CLICON_PLUGIN="routing_cli"; # Note, when switching to PT, change datamodel to only @datamodel -set @datamodel:ietf-ip, cli_set(); -merge @datamodel:ietf-ip, cli_merge(); -create @datamodel:ietf-ip, cli_create(); -delete("Delete a configuration item") @datamodel:ietf-ip, cli_del(); +set @datamodel:example, cli_set(); +merge @datamodel:example, cli_merge(); +create @datamodel:example, cli_create(); +delete("Delete a configuration item") @datamodel:example, cli_del(); validate("Validate changes"), cli_validate(); commit("Commit the changes"), cli_commit(); @@ -49,7 +49,7 @@ load("Load configuration from XML file") ("Filename (local file merge("Merge file with existent candidate"), load_config_file("filename", "merge"); } example("This is a comment") ("Just a random number"), mycallback("myarg"); -downcall("This is a downcall") , downcall(); +rpc("fib-route rpc") ("routing instance"), fib_route_rpc("myarg"); notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text"); no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml"); lock,cli_lock("candidate"); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 79838b36..408567b9 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -54,7 +54,7 @@ * - Cant use the symbols in this file because yacc needs token definitions */ enum rfc_6020{ - Y_ANYXML, + Y_ANYXML = 0, Y_ARGUMENT, Y_AUGMENT, Y_BASE, @@ -208,7 +208,8 @@ int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); int yang_print(FILE *f, yang_node *yn, int marginal); int yang_parse(clicon_handle h, const char *yang_dir, const char *module, const char *revision, yang_spec *ysp); -int yang_apply(yang_node *yn, yang_applyfn_t fn, void *arg); +int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, + void *arg); yang_node *yang_xpath_abs(yang_node *yn, char *xpath); yang_node *yang_xpath(yang_node *yn, char *xpath); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 43daa3c2..16576bb6 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -373,7 +373,7 @@ yn_each(yang_node *yn, * * @param[in] yn Yang node, current context node. * @param[in] keyword if 0 match any keyword - * @param[in] argument if NULL, match any argument. + * @param[in] argument String compare w wrgument. if NULL, match any. * This however means that if you actually want to match only a yang-stmt with * argument==NULL you cannot, but I have not seen any such examples. * @see yang_find_syntax @@ -1587,15 +1587,15 @@ yang_parse(clicon_handle h, clicon_dbspec_name_set(h, ymod->ys_argument); /* Resolve all types */ - yang_apply((yang_node*)ysp, ys_resolve_type, NULL); + yang_apply((yang_node*)ysp, Y_TYPE, ys_resolve_type, NULL); /* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */ if (yang_expand((yang_node*)ysp) < 0) goto done; - yang_apply((yang_node*)ymod, ys_flag_reset, (void*)YANG_FLAG_MARK); + yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK); /* Step 3: Go through parse tree and populate it with cv types */ - if (yang_apply((yang_node*)ysp, ys_populate, NULL) < 0) + if (yang_apply((yang_node*)ysp, -1, ys_populate, NULL) < 0) goto done; /* Step 4: Top-level augmentation of all modules */ @@ -1615,6 +1615,7 @@ yang_parse(clicon_handle h, * The tree is traversed depth-first, which at least guarantees that a parent is * traversed before a child. * @param[in] yn yang node + * @param[in] key yang keyword to use as filer or -1 for all * @param[in] fn Callback * @param[in] arg Argument * @retval -1 Error, aborted at first error encounter @@ -1625,12 +1626,13 @@ yang_parse(clicon_handle h, * { * return 0; * } - * yang_apply((yang_node*)ys, ys_fn, NULL); + * yang_apply((yang_node*)ys, Y_TYPE, ys_fn, NULL); * @endcode * @note do not delete or move around any children during this function */ int yang_apply(yang_node *yn, + enum rfc_6020 keyword, yang_applyfn_t fn, void *arg) { @@ -1641,9 +1643,15 @@ yang_apply(yang_node *yn, for (i=0; iyn_len; i++){ ys = yn->yn_stmt[i]; - if (fn(ys, arg) < 0) - goto done; - if ((ret = yang_apply((yang_node*)ys, fn, arg)) < 0) + if (keyword == -1 || keyword == ys->ys_keyword){ + if ((ret = fn(ys, arg)) < 0) + goto done; + if (ret > 0){ + retval = ret; + goto done; + } + } + if ((ret = yang_apply((yang_node*)ys, keyword, fn, arg)) < 0) goto done; if (ret > 0){ retval = ret; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 99100149..79874b88 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -218,8 +218,6 @@ ys_resolve_type(yang_stmt *ys, uint8_t fraction = 0; yang_stmt *resolved = NULL; - if (ys->ys_keyword != Y_TYPE) - return 0; if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, &options, &mincv, &maxcv, &pattern, &fraction) < 0) goto done; diff --git a/test/test1.sh b/test/test1.sh index e7f34af4..d60031ed 100755 --- a/test/test1.sh +++ b/test/test1.sh @@ -87,7 +87,7 @@ expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" "" expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" "" new "cli downcall" -expectfn "$clixon_cli -1f $clixon_cf -l o downcall \"This is a test =====\"" "^\"This is a test =====\"$" +expectfn "$clixon_cli -1f $clixon_cf -l o rpc ipv4" "^" new "Kill backend" # Check if still alive diff --git a/test/test4.sh b/test/test4.sh index 4ce09d69..78121d3f 100755 --- a/test/test4.sh +++ b/test/test4.sh @@ -10,7 +10,7 @@ clixon_netconf=clixon_netconf clixon_cli=clixon_cli cat < /tmp/test.yang -module ietf-ip{ +module example{ container x { list y { key "a b"; diff --git a/test/test6.sh b/test/test6.sh index e180e7b9..e3cf7b07 100755 --- a/test/test6.sh +++ b/test/test6.sh @@ -9,47 +9,21 @@ clixon_netconf=clixon_netconf clixon_cli=clixon_cli -cat < /tmp/rpc.yang -module ietf-ip{ - rpc fib-route { - input { - leaf name { - type string; - mandatory "true"; - } - leaf destination-address { - type string; - } - } - output { - container route { - leaf address{ - type string; - } - leaf address{ - type string; - } - } - } - } -} -EOF - # kill old backend (if any) new "kill old backend" -sudo clixon_backend -zf $clixon_cf -y /tmp/rpc +sudo clixon_backend -zf $clixon_cf if [ $? -ne 0 ]; then err fi new "start backend" # start new backend -sudo clixon_backend -If $clixon_cf -y /tmp/rpc +sudo clixon_backend -If $clixon_cf if [ $? -ne 0 ]; then err fi -new "netconf rpc (notyet)" -#expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/rpc" "]]>]]>" "^]]>]]>$" +new "netconf rpc" +expecteof "$clixon_netconf -qf $clixon_cf" "ipv4]]>]]>" "^]]>]]>$" new "Kill backend" # Check if still alive diff --git a/test/test7.sh b/test/test7.sh index d53a8f9f..34389b7c 100755 --- a/test/test7.sh +++ b/test/test7.sh @@ -10,7 +10,7 @@ clixon_netconf=clixon_netconf clixon_cli=clixon_cli cat < /tmp/leafref.yang -module ietf-ip{ +module example{ typedef admin-status{ type string; }