YANG Action (RFC 7950 Section 7.15)
See [Support for "action" statement](https://github.com/clicon/clixon/issues/101)
This commit is contained in:
parent
87c65c3541
commit
bdb516fec9
16 changed files with 583 additions and 104 deletions
|
|
@ -40,6 +40,11 @@ Planned: July 2022
|
|||
|
||||
### New features
|
||||
|
||||
* YANG Action (RFC 7950 Section 7.15)
|
||||
* Register action callback with `action_callback_register()`, see main example
|
||||
* Remains: check list keys, validate output
|
||||
* See [Support for "action" statement](https://github.com/clicon/clixon/issues/101)
|
||||
|
||||
* TEXT syntax parseable for loading files
|
||||
* Previously only supported output
|
||||
* TEXT output format changed (see API changes)
|
||||
|
|
|
|||
|
|
@ -867,7 +867,6 @@ from_client_kill_session(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Create a notification subscription
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xe Request: <rpc><xn></rpc>
|
||||
|
|
@ -1203,7 +1202,6 @@ from_client_hello(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! An internal clicon message has arrived from a client. Receive and dispatch.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] s Socket where message arrived. read from this.
|
||||
|
|
@ -1509,6 +1507,9 @@ backend_rpc_init(clicon_handle h)
|
|||
if (rpc_callback_register(h, from_client_kill_session, NULL,
|
||||
NETCONF_BASE_NAMESPACE, "kill-session") < 0)
|
||||
goto done;
|
||||
if (rpc_callback_register(h, action_callback_call, NULL,
|
||||
YANG_XML_NAMESPACE, "action") < 0)
|
||||
goto done;
|
||||
/* In backend_commit.? */
|
||||
if (rpc_callback_register(h, from_client_commit, NULL,
|
||||
NETCONF_BASE_NAMESPACE, "commit") < 0)
|
||||
|
|
|
|||
|
|
@ -353,8 +353,6 @@ netconf_edit_config(clicon_handle h,
|
|||
|
||||
/*! Get running configuration and device state information
|
||||
*
|
||||
*
|
||||
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
|
||||
* @param[out] xret Return XML, error or OK
|
||||
|
|
@ -726,7 +724,9 @@ netconf_rpc_dispatch(clicon_handle h,
|
|||
strcmp(xml_name(xe), "validate") == 0 || /* :validate */
|
||||
strcmp(xml_name(xe), "commit") == 0 || /* :candidate */
|
||||
strcmp(xml_name(xe), "cancel-commit") == 0 ||
|
||||
strcmp(xml_name(xe), "discard-changes") == 0){
|
||||
strcmp(xml_name(xe), "discard-changes") == 0 ||
|
||||
strcmp(xml_name(xe), "action") == 0
|
||||
){
|
||||
if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
* The example have the following optional arguments that you can pass as
|
||||
* argc/argv after -- in clixon_backend:
|
||||
* -a <..> Register callback for this yang action
|
||||
* -r enable the reset function
|
||||
* -s enable the state function
|
||||
* -S <file> read state data from file, otherwise construct it programmatically (requires -s)
|
||||
|
|
@ -66,7 +67,14 @@
|
|||
#include <clixon/clixon_backend.h>
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define BACKEND_EXAMPLE_OPTS "rsS:x:iuUt:v:"
|
||||
#define BACKEND_EXAMPLE_OPTS "a:rsS:x:iuUt:v:"
|
||||
|
||||
/*! Yang action
|
||||
* Start backend with -- -a <instance-id>
|
||||
* where instance-id points to an action node in some YANG
|
||||
* Hard-coded to action "reset" from RFC7950 7.15
|
||||
*/
|
||||
static char *_action_instanceid = NULL;
|
||||
|
||||
/*! Variable to control if reset code is run.
|
||||
* The reset code inserts "extra XML" which assumes ietf-interfaces is
|
||||
|
|
@ -357,6 +365,28 @@ example_copy_extra(clicon_handle h, /* Clicon handle */
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Action callback, example from RFC7950 7.15
|
||||
* Note callback is hardcoded C, while registration is controlled by -- -a option
|
||||
*/
|
||||
static int
|
||||
example_action_reset(clicon_handle h, /* Clicon handle */
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg, /* client_entry */
|
||||
void *regarg) /* Argument given at register */
|
||||
{
|
||||
int retval = -1;
|
||||
char *reset_at;
|
||||
|
||||
if ((reset_at = xml_find_body(xe, "reset-at")) != NULL)
|
||||
/* Just copy input to output */
|
||||
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><reset-finished-at xmlns=\"urn:example:server-farm\">%s</reset-finished-at></rpc-reply>",
|
||||
NETCONF_BASE_NAMESPACE, reset_at);
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Called to get state data from plugin by programmatically adding state
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
|
|
@ -1155,7 +1185,29 @@ example_reset(clicon_handle h,
|
|||
int
|
||||
example_start(clicon_handle h)
|
||||
{
|
||||
return 0;
|
||||
int retval = -1;
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *ya = NULL;
|
||||
|
||||
/* Register action callback, example from RFC7950 7.15
|
||||
* Can not be made in _init since YANG is not loaded
|
||||
* Note that callback is hardcoded here since it is C, but YANG and name of action
|
||||
* is not. It is enough to point via an schema-node id to the correct action,
|
||||
* such as "/sfarm:server/sfarm:reset"
|
||||
*/
|
||||
if (_action_instanceid){
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
if (yang_abs_schema_nodeid(yspec, _action_instanceid, &ya) == 0){
|
||||
if (ya && action_callback_register(h, ya, example_action_reset, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Plugin daemon.
|
||||
|
|
@ -1254,6 +1306,9 @@ clixon_plugin_init(clicon_handle h)
|
|||
optind = 1;
|
||||
while ((c = getopt(argc, argv, BACKEND_EXAMPLE_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'a':
|
||||
_action_instanceid = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
_reset = 1;
|
||||
break;
|
||||
|
|
@ -1346,6 +1401,7 @@ clixon_plugin_init(clicon_handle h)
|
|||
"copy-config"
|
||||
) < 0)
|
||||
goto done;
|
||||
|
||||
/* Upgrade callback: if you start the backend with -- -u you will get the
|
||||
* test interface example. Otherwise the auto-upgrade feature is enabled.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -163,3 +163,20 @@
|
|||
* If not set, client will exit
|
||||
*/
|
||||
#define PROTO_RESTART_RECONNECT
|
||||
|
||||
/*! Text output keys as identifiers instead of ordinary leafs
|
||||
* That is, given list "list" with key value "a", if set, the output of show config or save
|
||||
* as text command is:
|
||||
* list a {
|
||||
* val 42;
|
||||
* }
|
||||
* If not set, the output is:
|
||||
* list {
|
||||
* keyname a;
|
||||
* val 42;
|
||||
* }
|
||||
* The TEXT parser (ie load) accepts both formats.
|
||||
*/
|
||||
#define TEXT_LIST_KEYS
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -360,6 +360,26 @@ typedef struct clixon_plugin clixon_plugin_t;
|
|||
* The internal struct is defined in clixon_plugin.c */
|
||||
typedef struct plugin_context plugin_context_t;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
|
|
@ -402,6 +422,11 @@ int clixon_plugin_datastore_upgrade_all(clicon_handle h, const char *db, cxobj *
|
|||
/* rpc callback API */
|
||||
int rpc_callback_register(clicon_handle h, clicon_rpc_cb cb, void *arg, const char *ns, const char *name);
|
||||
int rpc_callback_call(clicon_handle h, cxobj *xe, void *arg, int *nrp, cbuf *cbret);
|
||||
|
||||
/* action callback API */
|
||||
int action_callback_register(clicon_handle h, yang_stmt *ya, clicon_rpc_cb cb, void *arg);
|
||||
int action_callback_call(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg);
|
||||
|
||||
/* upgrade callback API */
|
||||
int upgrade_callback_reg_fn(clicon_handle h, clicon_upgrade_cb cb, const char *strfn, const char *ns, void *arg);
|
||||
int upgrade_callback_call(clicon_handle h, cxobj *xt, char *ns, uint16_t op, uint32_t from, uint32_t to, cbuf *cbret);
|
||||
|
|
|
|||
|
|
@ -73,5 +73,7 @@ int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason);
|
|||
int yang_enum_int_value(cxobj *node, int32_t *val);
|
||||
int xml_copy_marked(cxobj *x0, cxobj *x1);
|
||||
int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp);
|
||||
int xml_rpc_isaction(cxobj *xn);
|
||||
int xml_find_action(cxobj *xn, int top, cxobj **xap);
|
||||
|
||||
#endif /* _CLIXON_XML_MAP_H_ */
|
||||
|
|
|
|||
|
|
@ -282,5 +282,7 @@ int yang_extension_value(yang_stmt *ys, char *name, char *ns, int *exist,
|
|||
int yang_sort_subelements(yang_stmt *ys);
|
||||
int yang_init(clicon_handle h);
|
||||
int yang_single_child_type(yang_stmt *ys, enum rfc_6020 subkeyw);
|
||||
void *yang_action_cb_get(yang_stmt *ys);
|
||||
int yang_action_cb_add(yang_stmt *ys, void *rc);
|
||||
|
||||
#endif /* _CLIXON_YANG_H_ */
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
|
|
@ -1144,7 +1125,7 @@ rpc_callback_call(clicon_handle h,
|
|||
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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,6 +184,8 @@ populate_self_parent(cxobj *xt,
|
|||
}
|
||||
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
|
||||
goto done;
|
||||
/* 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 */
|
||||
|
|
@ -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)
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -626,6 +626,7 @@ ys_free1(yang_stmt *ys,
|
|||
int self)
|
||||
{
|
||||
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)){
|
||||
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;
|
||||
if (ysmatch)
|
||||
goto match;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
||||
|
|
|
|||
|
|
@ -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*/
|
||||
|
|
|
|||
94
test/test_yang_action.sh
Executable file
94
test/test_yang_action.sh
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env bash
|
||||
# Yang action, use main exapl backend for registering action
|
||||
# See RFC 7950 7.15
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = "$0" ]; then exit 0; else return 0; fi
|
||||
|
||||
APPNAME=example
|
||||
|
||||
cfg=$dir/conf_yang.xml
|
||||
fyang=$dir/example-server-farm.yang
|
||||
|
||||
cat <<EOF > $cfg
|
||||
<clixon-config xmlns="http://clicon.org/config">
|
||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
|
||||
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
cat <<EOF > $fyang
|
||||
module example-server-farm {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:server-farm";
|
||||
prefix "sfarm";
|
||||
|
||||
import ietf-yang-types {
|
||||
prefix "yang";
|
||||
}
|
||||
|
||||
list server {
|
||||
key name;
|
||||
leaf name {
|
||||
type string;
|
||||
}
|
||||
action reset {
|
||||
input {
|
||||
leaf reset-at {
|
||||
type yang:date-and-time;
|
||||
mandatory true;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf reset-finished-at {
|
||||
type yang:date-and-time;
|
||||
mandatory true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
if [ $BE -ne 0 ]; then # Bring your own backend
|
||||
new "kill old backend"
|
||||
sudo clixon_backend -zf $cfg
|
||||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
new "start backend -s init -f $cfg -- -a /sfarm:server/sfarm:reset"
|
||||
start_backend -s init -f $cfg -- -a /sfarm:server/sfarm:reset
|
||||
fi
|
||||
|
||||
new "wait backend"
|
||||
wait_backend
|
||||
|
||||
rpc="<rpc $DEFAULTNS><action xmlns=\"urn:ietf:params:xml:ns:yang:1\"><server xmlns=\"urn:example:server-farm\"><name>apache-1</name><reset><reset-at>2014-07-29T13:42:00Z</reset-at></reset></server></action></rpc>"
|
||||
|
||||
new "netconf action"
|
||||
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "$rpc" "" "<rpc-reply $DEFAULTNS><reset-finished-at xmlns=\"urn:example:server-farm\">2014-07-29T13:42:00Z</reset-finished-at></rpc-reply>"
|
||||
|
||||
if [ $BE -ne 0 ]; then # Bring your own backend
|
||||
new "Kill backend"
|
||||
# Check if premature kill
|
||||
pid=$(pgrep -u root -f clixon_backend)
|
||||
if [ -z "$pid" ]; then
|
||||
err "backend already dead"
|
||||
fi
|
||||
# kill backend
|
||||
stop_backend -f $cfg
|
||||
fi
|
||||
|
||||
rm -rf "$dir"
|
||||
|
||||
new "endtest"
|
||||
endtest
|
||||
Loading…
Add table
Add a link
Reference in a new issue