diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24ae6967..26c595f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index ba48ba52..652a6e88 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -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:
@@ -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)
diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c
index ff8c0c32..4647e143 100644
--- a/apps/netconf/netconf_rpc.c
+++ b/apps/netconf/netconf_rpc.c
@@ -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 ... 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;
}
diff --git a/example/main/example_backend.c b/example/main/example_backend.c
index 289db5a6..4ad282d3 100644
--- a/example/main/example_backend.c
+++ b/example/main/example_backend.c
@@ -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 read state data from file, otherwise construct it programmatically (requires -s)
@@ -66,7 +67,14 @@
#include
/* 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
+ * 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: */
+ cbuf *cbret, /* Reply eg ... */
+ 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, "%s",
+ 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.
*/
diff --git a/include/clixon_custom.h b/include/clixon_custom.h
index 064d8973..b905ad93 100644
--- a/include/clixon_custom.h
+++ b/include/clixon_custom.h
@@ -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
+
+
diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h
index 5a53c777..0c4b447d 100644
--- a/lib/clixon/clixon_plugin.h
+++ b/lib/clixon/clixon_plugin.h
@@ -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);
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index 3691e7c8..8f6193d3 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -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_ */
diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h
index 2fbb474c..c51cd979 100644
--- a/lib/clixon/clixon_yang.h
+++ b/lib/clixon/clixon_yang.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_ */
diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c
index e7633ee1..aadacbb6 100644
--- a/lib/src/clixon_plugin.c
+++ b/lib/src/clixon_plugin.c
@@ -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: .
+ * @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
*/
diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c
index c9db689f..85e80724 100644
--- a/lib/src/clixon_validate.c
+++ b/lib/src/clixon_validate.c
@@ -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 */
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;
diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c
index 123e6e0d..e6d88787 100644
--- a/lib/src/clixon_xml_bind.c
+++ b/lib/src/clixon_xml_bind.c
@@ -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
*
* 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;
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index e8be1d40..752277b3 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -2025,3 +2025,66 @@ yang_check_when_xpath(cxobj *xn,
return retval;
}
+/*! Is XML node (ie under ) 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 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;
+}
diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c
index 4d0c6dd9..e0767132 100644
--- a/lib/src/clixon_yang.c
+++ b/lib/src/clixon_yang.c
@@ -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;
+}
diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h
index fa64d134..7570fa71 100644
--- a/lib/src/clixon_yang_internal.h
+++ b/lib/src/clixon_yang_internal.h
@@ -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_ */
diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c
index 4487e706..e4ae0e23 100644
--- a/lib/src/clixon_yang_parse_lib.c
+++ b/lib/src/clixon_yang_parse_lib.c
@@ -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*/
diff --git a/test/test_yang_action.sh b/test/test_yang_action.sh
new file mode 100755
index 00000000..aabc8713
--- /dev/null
+++ b/test/test_yang_action.sh
@@ -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 < $cfg
+
+ $cfg
+ ${YANG_INSTALLDIR}
+ $dir
+ $dir
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ /usr/local/var/$APPNAME/$APPNAME.sock
+ /usr/local/lib/$APPNAME/backend
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ /usr/local/var/$APPNAME
+
+EOF
+
+cat < $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="apache-12014-07-29T13:42:00Z"
+
+new "netconf action"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "$rpc" "" "2014-07-29T13:42:00Z"
+
+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