From bdb516fec9ed32e9c53cf19baf9846765ba41db4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 3 Jun 2022 11:14:58 +0200 Subject: [PATCH] YANG Action (RFC 7950 Section 7.15) See [Support for "action" statement](https://github.com/clicon/clixon/issues/101) --- CHANGELOG.md | 5 + apps/backend/backend_client.c | 5 +- apps/netconf/netconf_rpc.c | 6 +- example/main/example_backend.c | 60 ++++++++++- include/clixon_custom.h | 17 +++ lib/clixon/clixon_plugin.h | 25 +++++ lib/clixon/clixon_xml_map.h | 2 + lib/clixon/clixon_yang.h | 2 + lib/src/clixon_plugin.c | 161 +++++++++++++++++++++++----- lib/src/clixon_validate.c | 3 +- lib/src/clixon_xml_bind.c | 179 ++++++++++++++++++++++---------- lib/src/clixon_xml_map.c | 63 +++++++++++ lib/src/clixon_yang.c | 60 +++++++++-- lib/src/clixon_yang_internal.h | 2 +- lib/src/clixon_yang_parse_lib.c | 3 +- test/test_yang_action.sh | 94 +++++++++++++++++ 16 files changed, 583 insertions(+), 104 deletions(-) create mode 100755 test/test_yang_action.sh 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