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:
parent
f995f1e268
commit
e56cf607a3
17 changed files with 177 additions and 112 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
10
example/example.yang
Normal 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";
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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;
|
||||
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));
|
||||
/* Print result */
|
||||
xml_print(stdout, xml_child_i(xret, 0));
|
||||
retval = 0;
|
||||
done:
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (msg)
|
||||
free(msg);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
if (keyword == -1 || keyword == ys->ys_keyword){
|
||||
if ((ret = fn(ys, arg)) < 0)
|
||||
goto done;
|
||||
if ((ret = yang_apply((yang_node*)ys, fn, arg)) < 0)
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue