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
- 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.

View file

@ -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

View file

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

View file

@ -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

View file

@ -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
<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
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:
# <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:
# <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
# 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");
}
/*! IETF Routing fib-route rpc */
static int
routing_downcall(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 */
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>%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;
}
@ -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;

View file

@ -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("<rpc><myrouting>%s</myrouting></rpc>", 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, "<rpc><fib-route><routing-instance-name>%s</routing-instance-name></fib-route></rpc>", 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;
}

View file

@ -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:string>("Filename (local file
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
}
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");
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
lock,cli_lock("candidate");

View file

@ -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);

View file

@ -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; i<yn->yn_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;

View file

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

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" ""
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"
# Check if still alive

View file

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

View file

@ -9,47 +9,21 @@
clixon_netconf=clixon_netconf
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)
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" "<rpc><fib-route><name></name></fib-route></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf rpc"
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"
# Check if still alive

View file

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