YANG Action (RFC 7950 Section 7.15)

See [Support for "action" statement](https://github.com/clicon/clixon/issues/101)
This commit is contained in:
Olof hagsand 2022-06-03 11:14:58 +02:00
parent 87c65c3541
commit bdb516fec9
16 changed files with 583 additions and 104 deletions

View file

@ -66,7 +66,9 @@
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h"
#include "clixon_yang_module.h"
#include "clixon_netconf_lib.h"
#include "clixon_validate.h"
#include "clixon_plugin.h"
@ -94,26 +96,6 @@ struct clixon_plugin{
clixon_plugin_api cp_api;
};
/*
* RPC callbacks for both client/frontend and backend plugins.
* RPC callbacks are explicitly registered in the plugin_init() function
* with a tag and a function
* When the the tag is encountered, the callback is called.
* Primarily backend, but also netconf and restconf frontend plugins.
* CLI frontend so far have direct callbacks, ie functions in the cligen
* specification are directly dlsym:ed to the CLI plugin.
* It would be possible to use this rpc registering API for CLI plugins as well.
*
* When namespace and name match, the callback is made
*/
typedef struct {
qelem_t rc_qelem; /* List header */
clicon_rpc_cb rc_callback; /* RPC Callback */
void *rc_arg; /* Application specific argument to cb */
char *rc_namespace;/* Namespace to combine with name tag */
char *rc_name; /* Xml/json tag/name */
} rpc_callback_t;
/*
* Upgrade callbacks for backend upgrade of datastore
* Register upgrade callbacks in plugin_init() with a module and a "from" and "to"
@ -138,7 +120,6 @@ struct plugin_module_struct {
};
typedef struct plugin_module_struct plugin_module_struct;
/*! Get plugin handle containing plugin and callback lists
* @param[in] h Clicon handle
*/
@ -1043,7 +1024,7 @@ rpc_callback_dump(clicon_handle h)
}
#endif
/*! Register a RPC callback by appending a new RPC to the list
/*! Register a RPC callback by appending a new RPC to a global list
*
* @param[in] h clicon handle
* @param[in] cb Callback called
@ -1122,9 +1103,9 @@ rpc_callback_delete_all(clicon_handle h)
* @param[in] arg Domain-speific arg (eg client_entry)
* @param[out] nrp Number of callbacks handled: 0, 1, n (retval = 1) or NULL
* @param[out] cbret Return XML (as string in CLIgen buffer), error or OK
* @retval -1 Error
* @retval 0 Failed, error return in cbret
* @retval 1 OK, see nr
* @retval 1 OK, see nr
* @retval 0 Failed, error return in cbret
* @retval -1 Error
* @see rpc_callback_register which register a callback function
* @note that several callbacks can be registered. They need to cooperate on
* return values, ie if one writes cbret, the other needs to handle that by
@ -1137,14 +1118,14 @@ rpc_callback_call(clicon_handle h,
int *nrp,
cbuf *cbret)
{
int retval = -1;
int retval = -1;
rpc_callback_t *rc;
char *name;
char *prefix;
char *ns;
int nr = 0; /* How many callbacks */
plugin_module_struct *ms = plugin_module_struct_get(h);
void *wh = NULL;
void *wh;
int ret;
if (ms == NULL){
@ -1174,7 +1155,8 @@ rpc_callback_call(clicon_handle h,
}
rc = NEXTQ(rpc_callback_t *, rc);
} while (rc != ms->ms_rpc_callbacks);
if (nr){
/* action reply checked in action_callback_call */
if (nr && !xml_rpc_isaction(xe)){
if ((ret = rpc_reply_check(h, name, cbret)) < 0)
goto done;
if (ret == 0)
@ -1191,6 +1173,129 @@ rpc_callback_call(clicon_handle h,
goto done;
}
/*--------------------------------------------------------------------
* Action callback API. Reuse many of the same data structures as RPC, but
* context is given by a yang node
*/
/*! Register a RPC action callback by appending a new RPC to a yang action node
* @param[in] h clicon handle
* @param[in] ys YANG node where action resides
* @param[in] cb Callback
* @param[in] arg Domain-specific argument to send to callback
* @retval 0 OK
* @retval -1 Error
* @see rpc_callback_register which registers global callbacks
*/
int
action_callback_register(clicon_handle h,
yang_stmt *ya,
clicon_rpc_cb cb,
void *arg)
{
int retval = -1;
rpc_callback_t *rc = NULL;
char *name;
clicon_debug(1, "%s", __FUNCTION__);
if (ya == NULL){
clicon_err(OE_DB, EINVAL, "yang node is NULL");
goto done;
}
name = yang_argument_get(ya);
if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) {
clicon_err(OE_DB, errno, "malloc");
goto done;
}
memset(rc, 0, sizeof(*rc));
rc->rc_callback = cb;
rc->rc_arg = arg;
rc->rc_namespace = strdup(YANG_XML_NAMESPACE); // XXX
rc->rc_name = strdup(name);
if (yang_action_cb_add(ya, rc) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Search Action callbacks and invoke if XML match with tag
*
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[in] arg Domain-speific arg (eg client_entry)
* @param[out] nrp Number of callbacks handled: 0, 1, n (retval = 1) or NULL
* @param[out] cbret Return XML (as string in CLIgen buffer), error or OK
* @retval 1 OK, see nr
* @retval 0 Failed, error return in cbret
* @retval -1 Error
* @see rpc_callback_register which register a callback function
* @note that several callbacks can be registered. They need to cooperate on
* return values, ie if one writes cbret, the other needs to handle that by
* leaving it, replacing it or amending it.
*/
int
action_callback_call(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *xa = NULL;
yang_stmt *ya = NULL;
char *name;
int nr = 0; /* How many callbacks */
void *wh = NULL;
rpc_callback_t *rc;
clicon_debug(1, "%s", __FUNCTION__);
if (xml_find_action(xe, 1, &xa) < 0)
goto done;
if (xa == NULL){
if (netconf_operation_not_supported(cbret, "application", "Action not found") < 0)
goto done;
goto ok;
}
if ((ya = xml_spec(xa)) == NULL){
if (netconf_operation_not_supported(cbret, "application", "Action spec not found") < 0)
goto done;
goto ok;
}
name = xml_name(xa);
/* Action callback */
if ((rc = (rpc_callback_t *)yang_action_cb_get(ya)) != NULL){
do {
if (strcmp(rc->rc_name, name) == 0){
if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
goto done;
if (rc->rc_callback(h, xa, cbret, arg, rc->rc_arg) < 0){
clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
goto done;
goto done;
}
nr++;
if (plugin_context_check(h, &wh, rc->rc_name, __FUNCTION__) < 0)
goto done;
}
rc = NEXTQ(rpc_callback_t *, rc);
} while (rc != yang_action_cb_get(ya));
}
if (nr){
#ifdef NYI
if ((ret = rpc_action_reply_check(h, xe, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
#endif
}
ok:
retval = 1;
done:
return retval;
}
/*--------------------------------------------------------------------
* Upgrade callbacks for backend upgrade of datastore
*/

View file

@ -371,7 +371,6 @@ xml_yang_validate_rpc(clicon_handle h,
cxobj **xret)
{
int retval = -1;
yang_stmt *yn=NULL; /* rpc name */
cxobj *xn; /* rpc name */
char *rpcprefix;
char *namespace = NULL;
@ -393,7 +392,7 @@ xml_yang_validate_rpc(clicon_handle h,
xn = NULL;
/* xn is name of rpc, ie <rcp><xn/></rpc> */
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
if ((yn = xml_spec(xn)) == NULL){
if (xml_spec(xn) == NULL){
if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0)
goto done;
goto fail;

View file

@ -77,6 +77,7 @@
#include "clixon_netconf_lib.h"
#include "clixon_xml_sort.h"
#include "clixon_yang_type.h"
#include "clixon_xml_map.h"
#include "clixon_xml_bind.h"
/*
@ -183,31 +184,33 @@ populate_self_parent(cxobj *xt,
}
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done;
if ((y = yang_find_datanode(yparent, name)) == NULL){
if (_yang_unknown_anydata){
/* Add dummy Y_ANYDATA yang stmt, see ysp_add */
if ((y = yang_anydata_add(yparent, name)) < 0)
/* Special case since action is not a datanode */
if ((y = yang_find(yparent, Y_ACTION, name)) == NULL)
if ((y = yang_find_datanode(yparent, name)) == NULL){
if (_yang_unknown_anydata){
/* Add dummy Y_ANYDATA yang stmt, see ysp_add */
if ((y = yang_anydata_add(yparent, name)) < 0)
goto done;
xml_spec_set(xt, y);
retval = 2; /* treat as anydata */
clicon_log(LOG_WARNING,
"%s: %d: No YANG spec for %s, anydata used",
__FUNCTION__, __LINE__, name);
goto done;
xml_spec_set(xt, y);
retval = 2; /* treat as anydata */
clicon_log(LOG_WARNING,
"%s: %d: No YANG spec for %s, anydata used",
__FUNCTION__, __LINE__, name);
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed to find YANG spec of XML node: %s", name);
cprintf(cb, " with parent: %s", xml_name(xp));
if (ns)
cprintf(cb, " in namespace: %s", ns);
if (xerr &&
netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed to find YANG spec of XML node: %s", name);
cprintf(cb, " with parent: %s", xml_name(xp));
if (ns)
cprintf(cb, " in namespace: %s", ns);
if (xerr &&
netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
if (xerr &&
@ -516,6 +519,92 @@ xml_bind_yang0(cxobj *xt,
goto done;
}
/*! RPC-specific
*/
static int
xml_bind_yang_rpc_rpc(cxobj *x,
yang_stmt *yrpc,
char *rpcname,
cxobj **xerr)
{
int retval = -1;
cbuf *cb = NULL;
char *name;
cxobj *xc;
yang_stmt *yi = NULL; /* input */
int ret;
xml_spec_set(x, yrpc); /* required for validate */
if ((yi = yang_find(yrpc, Y_INPUT, NULL)) == NULL){
/* If no yang input spec but RPC has elements, return unknown element */
if (xml_child_nr_type(x, CX_ELMNT) != 0){
xc = xml_child_i_type(x, 0, CX_ELMNT); /* Pick first */
name = xml_name(xc);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unrecognized parameter: %s in rpc: %s", name, rpcname);
if (xerr &&
netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}
}
else{
/* xml_bind_yang need to have parent with yang spec for
* recursive population to work. Therefore, assign input yang
* to rpc level although not 100% intuitive */
xml_spec_set(x, yi);
if ((ret = xml_bind_yang(x, YB_PARENT, NULL, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;
goto done;
}
/*! Action-specific
*
* Find the innermost container or list containing an XML element that carries the name of the
* defined action.
* Only one action can be invoked in one rpc
* XXX if not more action, consider folding into calling function
*/
static int
xml_bind_yang_rpc_action(cxobj *xn,
yang_stmt *yspec,
cxobj **xerr)
{
int retval = -1;
int ret;
cxobj *xi;
yang_stmt *yi;;
if ((ret = xml_bind_yang(xn, YB_MODULE, yspec, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Special case: bind "action" node to module for validate code to work */
if ((xi = xml_child_i_type(xn, 0, CX_ELMNT)) != NULL &&
(yi = xml_spec(xi))){
xml_spec_set(xn, ys_module(yi));
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Find yang spec association of XML node for incoming RPC starting with <rpc>
*
* Incoming RPC has an "input" structure that is not taken care of by xml_bind_yang
@ -541,13 +630,11 @@ xml_bind_yang_rpc(cxobj *xrpc,
int retval = -1;
yang_stmt *yrpc = NULL; /* yang node */
yang_stmt *ymod=NULL; /* yang module */
yang_stmt *yi = NULL; /* input */
cxobj *x;
int ret;
char *opname; /* top-level netconf operation */
char *rpcname; /* RPC name */
char *name;
cbuf *cb = NULL;
cxobj *xc;
opname = xml_name(xrpc);
@ -607,6 +694,15 @@ xml_bind_yang_rpc(cxobj *xrpc,
x = NULL;
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
rpcname = xml_name(x);
if ((ret = xml_rpc_isaction(x)) < 0)
goto done;
if (ret == 1){
if ((ret = xml_bind_yang_rpc_action(x, yspec, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
goto ok;
} /* if not action fall through */
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
if (ymod == NULL){
@ -621,39 +717,14 @@ xml_bind_yang_rpc(cxobj *xrpc,
goto done;
goto fail;
}
xml_spec_set(x, yrpc); /* required for validate */
if ((yi = yang_find(yrpc, Y_INPUT, NULL)) == NULL){
/* If no yang input spec but RPC has elements, return unknown element */
if (xml_child_nr_type(x, CX_ELMNT) != 0){
xc = xml_child_i_type(x, 0, CX_ELMNT); /* Pick first */
name = xml_name(xc);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Unrecognized parameter: %s in rpc: %s", name, rpcname);
if (xerr &&
netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}
}
else{
/* xml_bind_yang need to have parent with yang spec for
* recursive population to work. Therefore, assign input yang
* to rpc level although not 100% intuitive */
xml_spec_set(x, yi);
if ((ret = xml_bind_yang(x, YB_PARENT, NULL, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if ((ret = xml_bind_yang_rpc_rpc(x, yrpc, rpcname, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;

View file

@ -2025,3 +2025,66 @@ yang_check_when_xpath(cxobj *xn,
return retval;
}
/*! Is XML node (ie under <rpc>) an action, ie name action and belong to YANG_XML_NAMESPACE?
* @param[in] xn XML node
* @retval 1 Yes, an action
* @retval 0 No, not an action
* @retval -1 Error
*/
int
xml_rpc_isaction(cxobj *xn)
{
int retval = -1;
char *ns = NULL;
if (strcmp(xml_name(xn), "action") != 0)
goto fail;
if (xml2ns(xn, xml_prefix(xn), &ns) < 0)
goto done;
if (strcmp(YANG_XML_NAMESPACE, ns) != 0)
goto fail;
retval = 1; // is action
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Find innermost node under <action> carrying the name of the defined action
* Find innermost container or list contains an XML element
* that carries the name of the defined action.
* @param[in] xn XML node
* @param[in] top If set, dont look for action node since top-level
* @param[out] xap Pointer to xml action node
* @retval 0 OK
* @retval -1 Error
* XXX: does not look at list key
*/
int
xml_find_action(cxobj *xn,
int top,
cxobj **xap)
{
int retval = -1;
cxobj *xc = NULL;
yang_stmt *yc;
while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) {
if ((yc = xml_spec(xc)) == NULL)
continue;
if (!top && yang_keyword_get(yc) == Y_ACTION){
*xap = xc;
break;
}
if (yang_keyword_get(yc) != Y_CONTAINER && yang_keyword_get(yc) != Y_LIST)
continue;
/* XXX check key */
if (xml_find_action(xc, 0, xap) < 0)
goto done;
break;
}
retval = 0;
done:
return retval;
}

View file

@ -625,7 +625,8 @@ int
ys_free1(yang_stmt *ys,
int self)
{
cg_var *cv;
cg_var *cv;
rpc_callback_t *rc;
if (ys->ys_argument){
free(ys->ys_argument);
@ -651,6 +652,14 @@ ys_free1(yang_stmt *ys,
free(ys->ys_stmt);
if (ys->ys_filename)
free(ys->ys_filename);
while((rc = ys->ys_action_cb) != NULL) {
DELQ(rc, ys->ys_action_cb, rpc_callback_t *);
if (rc->rc_namespace)
free(rc->rc_namespace);
if (rc->rc_name)
free(rc->rc_name);
free(rc);
}
if (self){
free(ys);
_stats_yang_nr--;
@ -1106,19 +1115,22 @@ yang_find_datanode(yang_stmt *yn,
ysmatch = yc;
}
if (ysmatch)
goto match;
goto match; // maybe break?
}
} /* Y_CHOICE */
else{
if (yang_datanode(ys)){
if (argument == NULL)
else if (yang_keyword_get(ys) == Y_INPUT ||
yang_keyword_get(ys) == Y_OUTPUT){ /* Look for its children */
if ((ysmatch = yang_find_datanode(ys, argument)) != NULL)
break;
}
else if (yang_datanode(ys)){
if (argument == NULL)
ysmatch = ys;
else
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
ysmatch = ys;
else
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
ysmatch = ys;
if (ysmatch)
goto match;
}
if (ysmatch)
goto match; // maybe break?
}
}
/* Special case: if not match and yang node is module or submodule, extend
@ -1146,6 +1158,7 @@ yang_find_datanode(yang_stmt *yn,
* @param[in] argument if NULL, match any(first) argument.
* @note XXX unify code with yang_find_datanode?
* @see yang_find_datanode
* @see yang_abs_schema_nodeid Top level function
*/
yang_stmt *
yang_find_schemanode(yang_stmt *yn,
@ -3994,3 +4007,28 @@ yang_single_child_type(yang_stmt *ys,
return 1; /* Passed all tests: yes you can hide this keyword */
}
/*! Get action callback list
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
*/
void *
yang_action_cb_get(yang_stmt *ys)
{
return ys->ys_action_cb;
}
/*! Add an action callback to YANG node
* XXX rc shouldnt really be void* but the type definitions in .h file got complicated
*/
int
yang_action_cb_add(yang_stmt *ys,
void *arg)
{
rpc_callback_t *rc = (rpc_callback_t *)arg;
if (rc == NULL){
clicon_err(OE_YANG, EINVAL, "arg is NULL");
return -1;
}
ADDQ(rc, ys->ys_action_cb);
return 0;
}

View file

@ -101,10 +101,10 @@ struct yang_stmt{
cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */
char *ys_filename; /* For debug/errors: filename (only (sub)modules) */
int ys_linenum; /* For debug/errors: line number (in ys_filename) */
rpc_callback_t *ys_action_cb; /* Action callback list, only for Y_ACTION */
/* Internal use */
int _ys_vector_i; /* internal use: yn_each */
};
#endif /* _CLIXON_YANG_INTERNAL_H_ */

View file

@ -85,7 +85,6 @@
#include "clixon_handle.h"
#include "clixon_file.h"
#include "clixon_yang.h"
#include "clixon_yang_internal.h"
#include "clixon_hash.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
@ -98,6 +97,8 @@
#include "clixon_yang_type.h"
#include "clixon_yang_parse.h"
#include "clixon_yang_cardinality.h"
#include "clixon_plugin.h"
#include "clixon_yang_internal.h"
#include "clixon_yang_parse_lib.h"
/* Size of json read buffer when reading from file*/