Added YANG RPC support, with example rpc documentation and testcase (test7.sh); Extended example with ietf-routing (not only ietf-ip).

This commit is contained in:
Olof hagsand 2017-07-20 10:54:31 +02:00
parent f995f1e268
commit e56cf607a3
17 changed files with 177 additions and 112 deletions

View file

@ -1,5 +1,9 @@
# Clixon CHANGELOG # 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 completion for generated cli leafrefs for both absolute and relatve paths.
- Added validation for leafref forward and backward references. - Added validation for leafref forward and backward references.

View file

@ -90,7 +90,7 @@ The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented w
- object-references - object-references
- if-feature - if-feature
- unique - unique
- rpc

View file

@ -196,26 +196,28 @@ catch:
return -1; 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 static int
ys_find_rpc(yang_stmt *ys, ys_find_rpc(yang_stmt *ys,
void *arg) void *arg)
{ {
cxobj *xn = (cxobj*)arg; find_rpc_arg *fra = (find_rpc_arg*)arg;
char *name = xml_name(xn);
if (ys->ys_keyword == Y_RPC && strcmp(name, ys->ys_argument) == 0){ if (strcmp(fra->name, ys->ys_argument) == 0){
/* fra->yrpc = ys;
* 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
*/
return 1; /* handled */ return 1; /* handled */
} }
return 0; return 0;
} }
/*! See if there is any callback registered for this tag /*! See if there is any callback registered for this tag
* *
* @param[in] h clicon handle * @param[in] h clicon handle
@ -234,6 +236,10 @@ netconf_plugin_callbacks(clicon_handle h,
int retval = -1; int retval = -1;
netconf_reg_t *nreg; netconf_reg_t *nreg;
yang_spec *yspec; yang_spec *yspec;
yang_stmt *yrpc;
yang_stmt *yinput;
find_rpc_arg fra = {0,0};
int ret;
if (deps != NULL){ if (deps != NULL){
nreg = deps; nreg = deps;
@ -249,8 +255,33 @@ netconf_plugin_callbacks(clicon_handle h,
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; 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; 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; retval = 0;
done: done:
return retval; return retval;

View file

@ -67,6 +67,7 @@ YANGSPECS += ietf-routing@2014-10-26.yang
YANGSPECS += ietf-ipv4-unicast-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-ipv6-unicast-routing@2014-10-26.yang
YANGSPECS += ietf-ipsec@2016-03-09.yang YANGSPECS += ietf-ipsec@2016-03-09.yang
YANGSPECS += example.yang
# Backend plugin # Backend plugin
BE_SRC = routing_backend.c BE_SRC = routing_backend.c

View file

@ -65,22 +65,45 @@ Routing notification
... ...
``` ```
## Extending ## Operation data
Clixon has an extension mechanism which can be used to make extended internal Clixon implements Yang RPC operations by an extension mechanism. The
netconf messages to the backend configuration engine. You may need this to extension mechanism enables you to add application-specific
make some special operation that is not covered by standard operations. It works by adding user-defined callbacks for added
netconf functions. The example has a simple "echo" downcall netconf operations. It is possible to use the extension mechanism
mechanism that simply echoes what is sent down and is included for independent of the yang rpc construct, but it is recommended to use
reference. A more realistic downcall would perform some action, such as that, and the example includes such an example:
reading some status.
Example: Example:
``` ```
cli> downcall "This is a string" cli> rpc ipv4
This is a string <rpc-reply>
<ok/>
</rpc-reply>
``` ```
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function).
```
<rpc>
<fib-route>
<routing-instance-name>ipv4</routing-instance-name>
</fib-route>
</rpc>
```
The backend in turn registers a callback (fib_route()) which handles the RPC.
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
```
## State data ## State data
Netconf <get> and restconf GET also returns state data, in contrast to Netconf <get> and restconf GET also returns state data, in contrast to

10
example/example.yang Normal file
View file

@ -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";
}

View file

@ -5,11 +5,12 @@ CLICON_CLI_MODE routing
# Option used to construct initial yang file: # Option used to construct initial yang file:
# <module>[@<revision>] # <module>[@<revision>]
CLICON_YANG_MODULE_MAIN ietf-ip #CLICON_YANG_MODULE_MAIN ietf-ip
CLICON_YANG_MODULE_MAIN example
# Option used to construct initial yang file: # Option used to construct initial yang file:
# <module>[@<revision>] # <module>[@<revision>]
CLICON_YANG_MODULE_REVISION 2014-06-16 #CLICON_YANG_MODULE_REVISION 2014-06-16
# Generate code for CLI completion of existing db symbols # Generate code for CLI completion of existing db symbols
# CLICON_CLI_GENMODEL_COMPLETION 0 # CLICON_CLI_GENMODEL_COMPLETION 0

View file

@ -119,14 +119,27 @@ notification_timer_setup(clicon_handle h)
return event_reg_timeout(t, notification_timer, h, "notification timer"); return event_reg_timeout(t, notification_timer, h, "notification timer");
} }
/*! IETF Routing fib-route rpc */
static int static int
routing_downcall(clicon_handle h, fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */ cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */ struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */ cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */ void *arg) /* Argument given at register */
{ {
cprintf(cbret, "<rpc-reply><ok>%s</ok></rpc-reply>", xml_body(xe)); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
/*! IETF Routing route-count rpc */
static int
route_count(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0; return 0;
} }
@ -173,10 +186,15 @@ plugin_init(clicon_handle h)
if (notification_timer_setup(h) < 0) if (notification_timer_setup(h) < 0)
goto done; goto done;
/* Register callback for netconf application-specific rpc call */ /* Register callback for routing rpc calls */
if (backend_rpc_cb_register(h, routing_downcall, if (backend_rpc_cb_register(h, fib_route,
NULL, 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) ) < 0)
goto done; goto done;
retval = 0; retval = 0;

View file

@ -92,40 +92,36 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
return retval; return retval;
} }
/*! get argument and send as string to backend as RPC (which returns the string) /*! Example "downcall": ietf-routing fib-route RPC */
*/
int int
downcall(clicon_handle h, fib_route_rpc(clicon_handle h,
cvec *vars, cvec *cvv,
cvec *argv) cvec *argv)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg = NULL; cg_var *instance;
char *str=""; cxobj *xtop = NULL;
cg_var *cv; cxobj *xrpc;
cxobj *xret=NULL; cxobj *xret = NULL;
cxobj *xerr;
cxobj *xdata;
if (cvec_len(vars)==2){ /* User supplied variable in CLI command */
if ((cv = cvec_i(vars, 1)) != NULL) instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */
str = cv_string_get(cv); /* Create XML for fib-route netconf RPC */
} if (clicon_xml_parse(&xtop, "<rpc><fib-route><routing-instance-name>%s</routing-instance-name></fib-route></rpc>", instance) < 0)
if ((msg = clicon_msg_encode("<rpc><myrouting>%s</myrouting></rpc>", str)) == NULL)
goto done; 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; goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ /* Print result */
clicon_rpc_generate_error(xerr); xml_print(stdout, xml_child_i(xret, 0));
goto done; retval = 0;
} done:
if ((xdata = xpath_first(xret, "//ok")) != NULL)
cli_output(stdout, "%s\n", xml_body(xdata));
retval = 0;
done:
if (xret) if (xret)
xml_free(xret); xml_free(xret);
if (msg) if (xtop)
free(msg); xml_free(xtop);
return retval; return retval;
} }

View file

@ -4,10 +4,10 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="routing_cli"; CLICON_PLUGIN="routing_cli";
# Note, when switching to PT, change datamodel to only @datamodel # Note, when switching to PT, change datamodel to only @datamodel
set @datamodel:ietf-ip, cli_set(); set @datamodel:example, cli_set();
merge @datamodel:ietf-ip, cli_merge(); merge @datamodel:example, cli_merge();
create @datamodel:ietf-ip, cli_create(); create @datamodel:example, cli_create();
delete("Delete a configuration item") @datamodel:ietf-ip, cli_del(); delete("Delete a configuration item") @datamodel:example, cli_del();
validate("Validate changes"), cli_validate(); validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit(); commit("Commit the changes"), cli_commit();
@ -49,7 +49,7 @@ load("Load configuration from XML file") <filename:string>("Filename (local file
merge("Merge file with existent candidate"), load_config_file("filename", "merge"); merge("Merge file with existent candidate"), load_config_file("filename", "merge");
} }
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg"); example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
downcall("This is a downcall") <str:rest>, downcall(); rpc("fib-route rpc") <instance:string>("routing instance"), fib_route_rpc("myarg");
notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text"); notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text");
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml"); no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
lock,cli_lock("candidate"); lock,cli_lock("candidate");

View file

@ -54,7 +54,7 @@
* - Cant use the symbols in this file because yacc needs token definitions * - Cant use the symbols in this file because yacc needs token definitions
*/ */
enum rfc_6020{ enum rfc_6020{
Y_ANYXML, Y_ANYXML = 0,
Y_ARGUMENT, Y_ARGUMENT,
Y_AUGMENT, Y_AUGMENT,
Y_BASE, 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_print(FILE *f, yang_node *yn, int marginal);
int yang_parse(clicon_handle h, const char *yang_dir, int yang_parse(clicon_handle h, const char *yang_dir,
const char *module, const char *revision, yang_spec *ysp); 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_abs(yang_node *yn, char *xpath);
yang_node *yang_xpath(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); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);

View file

@ -373,7 +373,7 @@ yn_each(yang_node *yn,
* *
* @param[in] yn Yang node, current context node. * @param[in] yn Yang node, current context node.
* @param[in] keyword if 0 match any keyword * @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 * 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. * argument==NULL you cannot, but I have not seen any such examples.
* @see yang_find_syntax * @see yang_find_syntax
@ -1587,15 +1587,15 @@ yang_parse(clicon_handle h,
clicon_dbspec_name_set(h, ymod->ys_argument); clicon_dbspec_name_set(h, ymod->ys_argument);
/* Resolve all types */ /* 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 */ /* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */
if (yang_expand((yang_node*)ysp) < 0) if (yang_expand((yang_node*)ysp) < 0)
goto done; 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 */ /* 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; goto done;
/* Step 4: Top-level augmentation of all modules */ /* 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 * The tree is traversed depth-first, which at least guarantees that a parent is
* traversed before a child. * traversed before a child.
* @param[in] yn yang node * @param[in] yn yang node
* @param[in] key yang keyword to use as filer or -1 for all
* @param[in] fn Callback * @param[in] fn Callback
* @param[in] arg Argument * @param[in] arg Argument
* @retval -1 Error, aborted at first error encounter * @retval -1 Error, aborted at first error encounter
@ -1625,12 +1626,13 @@ yang_parse(clicon_handle h,
* { * {
* return 0; * return 0;
* } * }
* yang_apply((yang_node*)ys, ys_fn, NULL); * yang_apply((yang_node*)ys, Y_TYPE, ys_fn, NULL);
* @endcode * @endcode
* @note do not delete or move around any children during this function * @note do not delete or move around any children during this function
*/ */
int int
yang_apply(yang_node *yn, yang_apply(yang_node *yn,
enum rfc_6020 keyword,
yang_applyfn_t fn, yang_applyfn_t fn,
void *arg) void *arg)
{ {
@ -1641,9 +1643,15 @@ yang_apply(yang_node *yn,
for (i=0; i<yn->yn_len; i++){ for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i]; ys = yn->yn_stmt[i];
if (fn(ys, arg) < 0) if (keyword == -1 || keyword == ys->ys_keyword){
goto done; if ((ret = fn(ys, arg)) < 0)
if ((ret = yang_apply((yang_node*)ys, fn, arg)) < 0) goto done;
if (ret > 0){
retval = ret;
goto done;
}
}
if ((ret = yang_apply((yang_node*)ys, keyword, fn, arg)) < 0)
goto done; goto done;
if (ret > 0){ if (ret > 0){
retval = ret; retval = ret;

View file

@ -218,8 +218,6 @@ ys_resolve_type(yang_stmt *ys,
uint8_t fraction = 0; uint8_t fraction = 0;
yang_stmt *resolved = NULL; yang_stmt *resolved = NULL;
if (ys->ys_keyword != Y_TYPE)
return 0;
if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved,
&options, &mincv, &maxcv, &pattern, &fraction) < 0) &options, &mincv, &maxcv, &pattern, &fraction) < 0)
goto done; goto done;

View file

@ -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" "" expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" ""
new "cli downcall" 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" "^<rpc-reply>"
new "Kill backend" new "Kill backend"
# Check if still alive # Check if still alive

View file

@ -10,7 +10,7 @@ clixon_netconf=clixon_netconf
clixon_cli=clixon_cli clixon_cli=clixon_cli
cat <<EOF > /tmp/test.yang cat <<EOF > /tmp/test.yang
module ietf-ip{ module example{
container x { container x {
list y { list y {
key "a b"; key "a b";

View file

@ -9,47 +9,21 @@
clixon_netconf=clixon_netconf clixon_netconf=clixon_netconf
clixon_cli=clixon_cli clixon_cli=clixon_cli
cat <<EOF > /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) # kill old backend (if any)
new "kill old backend" new "kill old backend"
sudo clixon_backend -zf $clixon_cf -y /tmp/rpc sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend" new "start backend"
# start new backend # start new backend
sudo clixon_backend -If $clixon_cf -y /tmp/rpc sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "netconf rpc (notyet)" new "netconf rpc"
#expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/rpc" "<rpc><fib-route><name></name></fib-route></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><fib-route><routing-instance-name>ipv4</routing-instance-name></fib-route></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Kill backend" new "Kill backend"
# Check if still alive # Check if still alive

View file

@ -10,7 +10,7 @@ clixon_netconf=clixon_netconf
clixon_cli=clixon_cli clixon_cli=clixon_cli
cat <<EOF > /tmp/leafref.yang cat <<EOF > /tmp/leafref.yang
module ietf-ip{ module example{
typedef admin-status{ typedef admin-status{
type string; type string;
} }