From a8e13047fc576c52c8e3ed21aa985a2c56a1c649 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Fri, 27 Jan 2023 12:38:56 +0100 Subject: [PATCH] * YANG schema mount RFC 8528, state data --- CHANGELOG.md | 9 +- README.md | 3 +- apps/backend/backend_get.c | 2 +- example/main/example_backend.c | 45 ++++ include/clixon_custom.h | 4 +- lib/clixon/clixon_plugin.h | 25 ++ lib/clixon/clixon_yang.h | 1 + lib/clixon/clixon_yang_module.h | 1 + lib/clixon/clixon_yang_parse_lib.h | 1 + lib/clixon/clixon_yang_schema_mount.h | 20 +- lib/src/clixon_datastore_write.c | 42 ++-- lib/src/clixon_plugin.c | 86 ++++++- lib/src/clixon_proto.c | 3 + lib/src/clixon_proto_client.c | 4 + lib/src/clixon_xml_bind.c | 29 +++ lib/src/clixon_yang.c | 37 ++- lib/src/clixon_yang_internal.h | 1 + lib/src/clixon_yang_module.c | 61 ++++- lib/src/clixon_yang_parse.y | 7 +- lib/src/clixon_yang_schema_mount.c | 313 ++++++++++++++++++++------ test/test_yang_schema_mount.sh | 22 +- 21 files changed, 572 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd55f84..7526106f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,11 +43,14 @@ Expected: beginning of 2023 ### New features -* YANG schema mount RFC 8528 (work in progress) +* YANG schema mount RFC 8528 + * Experimental * Restrictions: - * only schema-ref=inline, not shared-schema + * Only schema-ref=inline, not shared-schema + * Only presence containers can be mount-points + * New plugin callback: `ca_yang_mount` * Standards: RFC 8528 - * Enable `YANG_SCHEMA_MOUNT` + * To enable: define `YANG_SCHEMA_MOUNT` * Netconf monitoring RFC 6022 , part 2 * Datastores and sessions * Added clixon-specific transport identities: cli, snmp, netconf, restconf diff --git a/README.md b/README.md index c1e6f8f4..94ae57f7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ General Public License Version 2; you choose, see [LICENSE.md](LICENSE.md). Clixon has a master branch continuously tested with CI, but releases are made ca every second month. Latest 6.0.0 release is from November 2022. Next is planned for January 2023. See [CHANGELOG.md](CHANGELOG.md) release history. Clixon interaction is best done posting issues, pull requests, or joining the -Matrix channels: https://matrix.to/#/#clixondev:matrix.org for general info and discussion, and -https://matrix.to/#/#clixontech:matrix.org for technical details and issues. +Matrix clixon forum https://matrix.to/#/#clixonforum:matrix.org. Clixon is sponsored by [Rubicon Communications LLC(Netgate)](https://www.netgate.com/) and [Akamai Technologies, Inc.](https://www.akamai.com). diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 413d875f..0ad46550 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -298,7 +298,7 @@ get_statedata(clicon_handle h, goto fail; } #ifdef YANG_SCHEMA_MOUNT - if ((ret = schema_mounts_state_get(h, yspec, xpath, nsc, xret, &xerr)) < 0) + if ((ret = yang_schema_mount_statedata(h, yspec, xpath, nsc, xret, &xerr)) < 0) goto done; if (ret == 0){ if (clixon_netconf_internal_error(xerr, " . Internal error, schema_mounts_state_get returned invalid XML", NULL) < 0) diff --git a/example/main/example_backend.c b/example/main/example_backend.c index 1e540305..1e728857 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -880,6 +880,50 @@ example_upgrade(clicon_handle h, return retval; } +/*! Example YANG schema mount + * + * Given an XML mount-point xt, return XML yang-lib modules-set + * @param[in] h Clixon handle + * @param[in] xt XML mount-point in XML tree + * @param[out] yanglib XML yang-lib module-set tree + * @retval 0 OK + * @retval -1 Error + * XXX hardcoded to clixon-example@2022-11-01.yang regardless of xt + * @see RFC 8528 + */ +int +main_yang_mount(clicon_handle h, + cxobj *xt, + cxobj **yanglib) +{ + int retval = -1; + cbuf *cb = NULL; + + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, ""); + cprintf(cb, ""); + cprintf(cb, "mount"); + cprintf(cb, ""); + cprintf(cb, "clixon-example"); + cprintf(cb, "2022-11-01"); + cprintf(cb, "urn:example:urn"); + cprintf(cb, ""); + cprintf(cb, ""); + cprintf(cb, ""); + if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, yanglib, NULL) < 0) + goto done; + if (xml_rootchild(*yanglib, 0, yanglib) < 0) + goto done; + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + /*! Testcase module-specific upgrade function moving interfaces-state to interfaces * @param[in] h Clicon handle * @param[in] xn XML tree to be updated @@ -1302,6 +1346,7 @@ static clixon_plugin_api api = { .ca_trans_end=main_end, /* trans end */ .ca_trans_abort=main_abort, /* trans abort */ .ca_datastore_upgrade=example_upgrade, /* general-purpose upgrade. */ + .ca_yang_mount=main_yang_mount /* RFC 8528 schema mount */ }; /*! Backend plugin initialization diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 3d539fc2..ce4e5f27 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -192,8 +192,8 @@ */ #undef NETCONF_DEFAULT_RETRIEVAL_REPORT_ALL -/*! Development option for RFC 8528 YANG schema mount - * Work-in-progress +/*! RFC 8528 YANG schema mount + * Experimental * See also test/test_yang_schema_mount.sh */ #undef YANG_SCHEMA_MOUNT diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 6babbfd8..7f25161a 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -269,6 +269,26 @@ typedef char *(cli_prompthook_t)(clicon_handle, char *mode); */ typedef int (datastore_upgrade_t)(clicon_handle h, const char *db, cxobj *xt, modstate_diff_t *msd); +/*! YANG schema mount + * + * Given an XML mount-point xt, return XML yang-lib modules-set + * Return yanglib as XML tree on the RFC8525 form: + * + * + * ... + * ... + * + * + * No need to YANG bind. + * @param[in] h Clixon handle + * @param[in] xt XML mount-point in XML tree + * @param[out] yanglib XML yang-lib module-set tree. Freed by caller. + * @retval 0 OK + * @retval -1 Error + * @see RFC 8528 (schema-mount) and RFC 8525 (yang-lib) + */ +typedef int (yang_mount_t)(clicon_handle h, cxobj *xt, cxobj **yanglib); + /*! Startup status for use in startup-callback * Note that for STARTUP_ERR and STARTUP_INVALID, running runs in failsafe mode * and startup contains the erroneous or invalid database. @@ -322,6 +342,7 @@ struct clixon_plugin_api{ trans_cb_t *cb_trans_end; /* Transaction completed */ trans_cb_t *cb_trans_abort; /* Transaction aborted */ datastore_upgrade_t *cb_datastore_upgrade; /* General-purpose datastore upgrade */ + yang_mount_t *cb_yang_mount; /* RFC 8528 schema mount */ } cau_backend; } u; }; @@ -344,6 +365,7 @@ struct clixon_plugin_api{ #define ca_trans_end u.cau_backend.cb_trans_end #define ca_trans_abort u.cau_backend.cb_trans_abort #define ca_datastore_upgrade u.cau_backend.cb_datastore_upgrade +#define ca_yang_mount u.cau_backend.cb_yang_mount /* * Macros @@ -419,6 +441,9 @@ int clixon_plugin_extension_all(clicon_handle h, yang_stmt *yext, yang_stmt *ys) int clixon_plugin_datastore_upgrade_one(clixon_plugin_t *cp, clicon_handle h, const char *db, cxobj *xt, modstate_diff_t *msd); int clixon_plugin_datastore_upgrade_all(clicon_handle h, const char *db, cxobj *xt, modstate_diff_t *msd); +int clixon_plugin_yang_mount_one(clixon_plugin_t *cp, clicon_handle h, cxobj *xt, cxobj **yanglib); +int clixon_plugin_yang_mount_all(clicon_handle h, cxobj *xt, cxobj **yanglib); + /* 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); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 0e3e0342..35a8dd3c 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -214,6 +214,7 @@ cg_var *yang_cv_get(yang_stmt *ys); int yang_cv_set(yang_stmt *ys, cg_var *cv); cvec *yang_cvec_get(yang_stmt *ys); int yang_cvec_set(yang_stmt *ys, cvec *cvv); +cg_var *yang_cvec_add(yang_stmt *ys, enum cv_type type, char *name); uint16_t yang_flag_get(yang_stmt *ys, uint16_t flag); int yang_flag_set(yang_stmt *ys, uint16_t flag); int yang_flag_reset(yang_stmt *ys, uint16_t flag); diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 121c4ab1..a8686615 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -78,5 +78,6 @@ yang_stmt *yang_find_module_by_name_revision(yang_stmt *yspec, const char *name, yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name); int yang_metadata_annotation_check(cxobj *x, yang_stmt *ymod, int *ismeta); int yang_metadata_init(clicon_handle h); +int yang_lib2yspec(clicon_handle h, cxobj *yanglib,yang_stmt *yspec); #endif /* _CLIXON_YANG_MODULE_H_ */ diff --git a/lib/clixon/clixon_yang_parse_lib.h b/lib/clixon/clixon_yang_parse_lib.h index e51b8c9f..fe3efb05 100644 --- a/lib/clixon/clixon_yang_parse_lib.h +++ b/lib/clixon/clixon_yang_parse_lib.h @@ -52,6 +52,7 @@ * Prototypes */ yang_stmt *yang_parse_file(FILE *fp, const char *name, yang_stmt *ysp); +int yang_file_find_match(clicon_handle h, const char *module, const char *revision, cbuf *fbuf); yang_stmt *yang_parse_filename(const char *filename, yang_stmt *ysp); int yang_parse_post(clicon_handle h, yang_stmt *yspec, int modmin); int yang_spec_parse_module(clicon_handle h, const char *module, diff --git a/lib/clixon/clixon_yang_schema_mount.h b/lib/clixon/clixon_yang_schema_mount.h index 412c5a58..01be5f47 100644 --- a/lib/clixon/clixon_yang_schema_mount.h +++ b/lib/clixon/clixon_yang_schema_mount.h @@ -44,12 +44,26 @@ */ #define YANG_SCHEMA_MOUNT_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount" +/* Limitations/deviations from RFC 8528 */ +/*! Only support YANG presende containers as mount-points + * This is a limitation of othe current implementation + */ +#define YANG_SCHEMA_MOUNT_ONLY_PRESENCE_CONTAINERS + +/*! Force add ietf-yang-library@2019-01-04 on all mount-points + * This is a limitation of othe current implementation + */ +#define YANG_SCHEMA_MOUNT_YANG_LIB_FORCE + /* * Prototypes */ int yang_schema_mount_point(yang_stmt *y); - -int schema_mounts_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, cxobj **xret, cxobj **xerr); -int yang_schema_unknown(clicon_handle h, yang_stmt *yext, yang_stmt *ys); +int xml_yang_mount_get(cxobj *x, yang_stmt **yspec); +int xml_yang_mount_set(cxobj *x, yang_stmt *yspec); +int xml_yang_mount_freeall(cvec *cvv); +int yang_schema_mount_statedata(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, cxobj **xret, cxobj **xerr); +int yang_schema_yanglib_parse_mount(clicon_handle h, cxobj *xt); +int yang_schema_get_child(clicon_handle h, cxobj *x1, cxobj *x1c, yang_stmt **yc); #endif /* _CLIXON_YANG_SCHEMA_MOUNT_H_ */ diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 1c1cb30c..defc8343 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -75,6 +75,7 @@ #include "clixon_netconf_lib.h" #include "clixon_yang_type.h" #include "clixon_yang_module.h" +#include "clixon_yang_schema_mount.h" #include "clixon_xml_nsctx.h" #include "clixon_xml_io.h" #include "clixon_xml_default.h" @@ -854,21 +855,25 @@ text_modify(clicon_handle h, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child by child matching */ - yc = yang_find_datanode(y0, x1cname); - if (yc == NULL){ - if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1){ - /* Add dummy Y_ANYDATA yang stmt, see ysp_add */ - if ((yc = yang_anydata_add(y0, x1cname)) < 0) - goto done; - xml_spec_set(x1c, yc); - clicon_log(LOG_WARNING, - "%s: %d: No YANG spec for %s, anydata used", - __FUNCTION__, __LINE__, x1cname); - } - else{ - if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) - goto done; - goto fail; + if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ +#ifdef YANG_SCHEMA_MOUNT + yc = xml_spec(x1c); +#endif + if (yc == NULL){ + if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1){ + /* Add dummy Y_ANYDATA yang stmt, see ysp_add */ + if ((yc = yang_anydata_add(y0, x1cname)) < 0) + goto done; + xml_spec_set(x1c, yc); + clicon_log(LOG_WARNING, + "%s: %d: No YANG spec for %s, anydata used", + __FUNCTION__, __LINE__, x1cname); + } + else{ + if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) + goto done; + goto fail; + } } } /* There is a cornercase (eg augment) of multi-namespace trees where @@ -898,8 +903,11 @@ text_modify(clicon_handle h, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x0c = x0vec[i++]; x1cname = xml_name(x1c); - - yc = yang_find_datanode(y0, x1cname); + if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ +#ifdef YANG_SCHEMA_MOUNT + yc = xml_spec(x1c); +#endif + } if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t, yc, op, username, xnacm, permit, cbret)) < 0) diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 0d9d3daa..a585c680 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -547,9 +547,9 @@ plugin_context_get(void) * @param[in,out] wh Either: NULL for init, will be assigned, OR previous handle (will be freed) * @param[in] name Name of plugin for logging. Can be other name, context dependent * @param[in] fn Typically name of callback, or caller function - * @retval -1 Error - * @retval 0 Fail, log on syslog using LOG_WARNING * @retval 1 OK + * @retval 0 Fail, log on syslog using LOG_WARNING + * @retval -1 Error * @note Only logs error, does not generate error * @note name and fn are context dependent, since the env of callback calls are very different * @see plugin_context_get @@ -786,9 +786,9 @@ clixon_plugin_exit_all(clicon_handle h) * @param[out] authp NULL: Credentials failed, no user set (401 returned). * String: Credentials OK, the associated user, must be mallloc:ed * Parameter signtificant only if retval is 1/OK - * @retval -1 Fatal error - * @retval 0 Ignore, undecided, not handled, same as no callback * @retval 1 OK, see authp parameter on result. + * @retval 0 Ignore, undecided, not handled, same as no callback + * @retval -1 Fatal error * @note If authenticated either a callback was called and clicon_username_set() * Or no callback was found. */ @@ -833,9 +833,9 @@ clixon_plugin_auth_one(clixon_plugin_t *cp, * @param[out] authp NULL: Credentials failed, no user set (401 returned). * String: Credentials OK, the associated user, must be mallloc:ed * Parameter signtificant only if retval is 1/OK - * @retval -1 Fatal error - * @retval 0 Ignore, undecided, not handled, same as no callback * @retval 1 OK, see authp parameter for result. + * @retval 0 Ignore, undecided, not handled, same as no callback + * @retval -1 Fatal error * @note If authp returns string, it should be malloced */ int @@ -981,8 +981,8 @@ clixon_plugin_datastore_upgrade_one(clixon_plugin_t *cp, * @param[in] db Name of datastore, eg "running", "startup" or "tmp" * @param[in] xt XML tree. Upgrade this "in place" * @param[in] msd Module-state diff, info on datastore module-state - * @retval -1 Error * @retval 0 OK + * @retval -1 Error * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism */ int @@ -1003,6 +1003,72 @@ clixon_plugin_datastore_upgrade_all(clicon_handle h, return retval; } +/*! Call plugin YANG schema mount + * + + * (No need to be yang bound? + * @param[in] cp Plugin handle + * @param[in] h Clixon handle + * @param[in] xt XML mount-point in XML tree + * @param[out] yanglib XML yang-lib module-set tree + * @retval 0 OK + * @retval -1 Error + */ +int +clixon_plugin_yang_mount_one(clixon_plugin_t *cp, + clicon_handle h, + cxobj *xt, + cxobj **yanglib) +{ + int retval = -1; + yang_mount_t *fn; + void *wh = NULL; + + if ((fn = cp->cp_api.ca_yang_mount) != NULL){ + wh = NULL; + if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0) + goto done; + if (fn(h, xt, yanglib) < 0) { + if (clicon_errno < 0) + clicon_log(LOG_WARNING, "%s: Internal error: Yang mount callback in plugin: %s returned -1 but did not make a clicon_err call", + __FUNCTION__, cp->cp_name); + goto done; + } + if (plugin_context_check(h, &wh, cp->cp_name, __FUNCTION__) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Call plugin YANG schema mount in all plugins, break at first return + * + * @param[in] h Clixon handle + * @param[in] xt XML mount-point in XML tree + * @param[out] yanglib XML yang-lib module-set tree (freed by caller) + * @retval 0 OK + * @retval -1 Error + */ +int +clixon_plugin_yang_mount_all(clicon_handle h, + cxobj *xt, + cxobj **yanglib) +{ + int retval = -1; + clixon_plugin_t *cp = NULL; + + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if (clixon_plugin_yang_mount_one(cp, h, xt, yanglib) < 0) + goto done; + if (*yanglib) + break; + } + retval = 0; + done: + return retval; +} + /*-------------------------------------------------------------------- * RPC callbacks for both client/frontend and backend plugins. */ @@ -1166,7 +1232,7 @@ rpc_callback_call(clicon_handle h, *nrp = nr; retval = 1; /* 0: none found, >0 nr of handlers called */ done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval); return retval; fail: retval = 0; @@ -1373,9 +1439,9 @@ upgrade_callback_delete_all(clicon_handle h) * @param[in] from From revision on the form YYYYMMDD (if DEL or CHANGE) * @param[in] to To revision on the form YYYYMMDD (if ADD or CHANGE) * @param[out] cbret Return XML (as string in CLIgen buffer), on invalid - * @retval -1 Error - * @retval 0 Invalid - cbret contains reason as netconf * @retval 1 OK + * @retval 0 Invalid - cbret contains reason as netconf + * @retval -1 Error * @see upgrade_callback_reg_fn which registers the callbacks */ int diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index ce987aec..a02f9b1f 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -433,6 +433,7 @@ clicon_msg_rcv(int s, clicon_debug(CLIXON_DBG_MSG, "Recv: %s", (*msg)->op_body); retval = 0; done: + clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval); if (0) set_signal(SIGINT, oldhandler, NULL); return retval; @@ -643,6 +644,7 @@ clicon_rpc(int sock, struct clicon_msg *reply = NULL; char *data = NULL; + clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); if (clicon_msg_send(sock, msg) < 0) goto done; if (clicon_msg_rcv(sock, &reply, eof) < 0) @@ -658,6 +660,7 @@ clicon_rpc(int sock, ok: retval = 0; done: + clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval); if (reply) free(reply); return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index d6180615..9348a6db 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -198,6 +198,7 @@ clicon_rpc_msg(clicon_handle h, int s = -1; int eof = 0; + clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); #ifdef RPC_USERNAME_ASSERT assert(strstr(msg->op_body, "username")!=NULL); /* XXX */ #endif @@ -245,6 +246,7 @@ clicon_rpc_msg(clicon_handle h, } retval = 0; done: + clicon_debug(CLIXON_DBG_DETAIL, "%s %d", __FUNCTION__, retval); if (retdata) free(retdata); if (xret) @@ -961,6 +963,7 @@ clicon_rpc_get(clicon_handle h, yang_stmt *yspec; cvec *nscd = NULL; + clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); if (session_id_check(h, &session_id) < 0) goto done; if ((cb = cbuf_new()) == NULL){ @@ -1044,6 +1047,7 @@ clicon_rpc_get(clicon_handle h, } retval = 0; done: + clicon_debug(CLIXON_DBG_DETAIL, "%s %d", __FUNCTION__, retval); if (nscd) cvec_free(nscd); if (cb) diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 3ff35f1f..fff0c56b 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -69,6 +69,8 @@ #include "clixon_options.h" #include "clixon_data.h" #include "clixon_yang_module.h" +#include "clixon_yang_schema_mount.h" +#include "clixon_plugin.h" #include "clixon_xml_nsctx.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" @@ -451,7 +453,34 @@ xml_bind_yang0_opt(clicon_handle h, goto ok; strip_body_objects(xt); ybc = YB_PARENT; +#ifdef YANG_SCHEMA_MOUNT // Maybe in populate? + yspec1 = NULL; + if ((ret = xml_yang_mount_get(xt, &yspec1)) < 0) + goto done; + if (ret == 0) + yspec1 = yspec; + else{ + if (yspec1) + ybc = YB_MODULE; + else if (h == NULL) + goto ok; /* treat as anydata */ + else{ + if ((ret = yang_schema_yanglib_parse_mount(h, xt)) < 0) + goto done; + if (ret == 0) + goto ok; + /* Try again */ + if ((ret = xml_yang_mount_get(xt, &yspec1)) < 0) + goto done; + if (yspec1) + ybc = YB_MODULE; + else + goto ok; + } + } +#else yspec1 = yspec; +#endif xc = NULL; /* Apply on children */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) { /* It is xml2ns in populate_self_parent that needs improvement */ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index e5a98c2c..dc4f5989 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -307,11 +307,10 @@ yang_cvec_set(yang_stmt *ys, * @retval cv The new cligen variable * @retval NULL Error */ -static cg_var * +cg_var * yang_cvec_add(yang_stmt *ys, enum cv_type type, char *name) - { cg_var *cv; cvec *cvv; @@ -657,18 +656,27 @@ ys_free1(yang_stmt *ys, cg_var *cv; rpc_callback_t *rc; - if (ys->ys_argument){ - free(ys->ys_argument); - ys->ys_argument = NULL; - } if ((cv = yang_cv_get(ys)) != NULL){ yang_cv_set(ys, NULL); /* only frees on replace */ cv_free(cv); } if (ys->ys_cvec){ +#ifdef YANG_SCHEMA_MOUNT + /* Schema mount uses cvec in unknown to keep track of all yspecs + * Freed here once. + */ + if (yang_keyword_get(ys) == Y_UNKNOWN && + strcmp(yang_argument_get(ys), "yangmnt:mount-point")==0){ + xml_yang_mount_freeall(ys->ys_cvec); + } +#endif cvec_free(ys->ys_cvec); ys->ys_cvec = NULL; } + if (ys->ys_argument){ + free(ys->ys_argument); + ys->ys_argument = NULL; + } if (ys->ys_typecache){ yang_type_cache_free(ys->ys_typecache); ys->ys_typecache = NULL; @@ -1131,16 +1139,7 @@ yang_find_datanode(yang_stmt *yn, yang_stmt *yspec; yang_stmt *ysmatch = NULL; char *name; -#ifdef YANG_SCHEMA_MOUNT - int ret; - - /* Sanity-check mount-point extension */ - if ((ret = yang_schema_mount_point(yn)) < 0) - goto done; - if (ret == 1){ - ; // NYI - } -#endif + ys = NULL; while ((ys = yn_each(yn, ys)) != NULL){ if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */ @@ -1904,7 +1903,7 @@ yang_spec_print(FILE *f, fprintf(f, "%s", yang_key2str(ym->ys_keyword)); fprintf(f, " %s", ym->ys_argument); if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){ - fprintf(f, "@%u", cv_uint32_get(yang_cv_get(yrev))); + fprintf(f, "@%s", yang_argument_get(yrev)); } fprintf(f, ".yang"); fprintf(f, "\n"); @@ -2754,10 +2753,6 @@ ys_populate_unknown(clicon_handle h, clicon_debug(1, "plugin_extension() failed"); return -1; } -#endif -#ifdef YANG_SCHEMA_MOUNT - if (yang_schema_unknown(h, yext, ys) < 0) - goto done; #endif /* Make extension callbacks that may alter yang structure * Note: this may be a "layering" violation: assuming plugins are loaded diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index a240df2f..ff809ccb 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -96,6 +96,7 @@ struct yang_stmt{ types as : list Y_UNIQUE: vector of descendant schema node ids Y_EXTENSION: vector of instantiated UNKNOWNSo + Y_UNKNOWN: app-dep: yang-mount-points */ yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index b55e0b3b..51c9b074 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -210,8 +210,10 @@ yang_modules_state_build(clicon_handle h, yang_stmt *ysub; char *name; - if ((ylib = yang_find(yspec, Y_MODULE, module)) == NULL && - (ylib = yang_find(yspec, Y_SUBMODULE, module)) == NULL){ + /* In case of several mountpoints, this is always the top-level */ + if ((ylib = yang_find(yspec, Y_MODULE, module)) == NULL + /* && (ylib = yang_find(yspec0, Y_SUBMODULE, module)) == NULL */ + ){ clicon_err(OE_YANG, 0, "%s not found", module); goto done; } @@ -219,7 +221,6 @@ yang_modules_state_build(clicon_handle h, clicon_err(OE_YANG, 0, "%s yang namespace not found", module); goto done; } - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){ cprintf(cb,"", yang_argument_get(yns)); cprintf(cb,"%s", msid); @@ -671,7 +672,7 @@ yang_find_module_by_namespace_revision(yang_stmt *yspec, * @note a module may have many revisions, but only the first is significant */ yang_stmt * -yang_find_module_by_name_revision(yang_stmt *yspec, +yang_find_module_by_name_revision(yang_stmt *yspec, const char *name, const char *rev) { @@ -824,3 +825,55 @@ yang_metadata_init(clicon_handle h) done: return retval; } + +/*! Given a yang-lib module-set XML tree, parse all modules into an yspec + * + * This function is used where a yang-lib module-set is available to populate an + * XML mount-point. + * @param[in] h Clicon handle + * @param[in] xylib yang-lib XML tree on the form ... + * @param[in] yspec Will be populated with YANGs, is consumed + * @retval 1 OK + * @retval 0 Parse error + * @retval -1 Error + * @see xml_schema_add_mount_points + */ +int +yang_lib2yspec(clicon_handle h, + cxobj *yanglib, + yang_stmt *yspec) +{ + int retval = -1; + cxobj *xi; + char *name; + char *revision; + cvec *nsc = NULL; + cxobj **vec = NULL; + size_t veclen; + int i; + + if (xpath_vec(yanglib, nsc, "module-set/module", &vec, &veclen) < 0) + goto done; + for (i=0; i GROUPING id-arg-str ;"); } + | K_GROUPING identifier_str { if (ysp_add_push(_yy, Y_GROUPING, $2, NULL) == NULL) _YYERROR("grouping_stmt"); } '{' grouping_substmts '}' { if (ystack_pop(_yy) < 0) _YYERROR("grouping_stmt"); - _PARSE_DEBUG("grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } + _PARSE_DEBUG("grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } ; grouping_substmts : grouping_substmts grouping_substmt diff --git a/lib/src/clixon_yang_schema_mount.c b/lib/src/clixon_yang_schema_mount.c index e79f2ee8..5e95fdf1 100644 --- a/lib/src/clixon_yang_schema_mount.c +++ b/lib/src/clixon_yang_schema_mount.c @@ -32,6 +32,19 @@ ***** END LICENSE BLOCK ***** * RFC 8525 Yang schema mount support + * + * Structure of mount-points in XML: + * YANG mount extentsion -->* YANG unknown mount stmt -->* XML mount-points + * | + * cvec mapping xpath->yspec mountpoint + * + * The calls into this code are: + * 1. yang_schema_mount_point() Check that a yang nod eis mount-point + * 2. xml_yang_mount_get(): from xml_bind_yang and xmldb_put + * 3. xml_yang_mount_freeall(): from ys_free1 when deallocatin YANG trees + * 4. yang_schema_mount_statedata(): from get_common/get_statedata to retrieve system state + * 5. yang_schema_yanglib_parse_mount(): from xml_bind_yang to parse and mount + * 6. yang_schema_get_child(): from xmldb_put/text_modify when adding new XML nodes */ #ifdef HAVE_CONFIG_H @@ -43,6 +56,8 @@ #include #include #include +#include +#include /* cligen */ #include @@ -50,18 +65,25 @@ /* clixon */ #include "clixon_queue.h" #include "clixon_hash.h" +#include "clixon_string.h" #include "clixon_handle.h" #include "clixon_err.h" #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_io.h" +#include "clixon_xml_map.h" +#include "clixon_data.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_yang_module.h" +#include "clixon_yang_parse_lib.h" +#include "clixon_plugin.h" +#include "clixon_xml_bind.h" #include "clixon_netconf_lib.h" - #include "clixon_yang_schema_mount.h" -/*! Check if y is a RFC 8525 YANG schema mount +/*! Check if YANG node is a RFC 8525 YANG schema mount * * Check if: * - y is CONTAINER or LIST, AND @@ -87,7 +109,14 @@ yang_schema_mount_point(yang_stmt *y) goto done; } keyw = yang_keyword_get(y); - if (keyw != Y_CONTAINER && keyw != Y_LIST) + if (keyw != Y_CONTAINER +#ifndef YANG_SCHEMA_MOUNT_ONLY_PRESENCE_CONTAINERS + && keyw != Y_LIST +#endif +#if 0 /* See this in some standard YANGs but RFC 8528 does not allow it */ + && keyw != Y_ANYDATA +#endif + ) goto fail; if (yang_extension_value(y, "mount-point", YANG_SCHEMA_MOUNT_NAMESPACE, &exist, &value) < 0) goto done; @@ -103,6 +132,113 @@ yang_schema_mount_point(yang_stmt *y) goto done; } +/*! Get yangspec mount-point + * + * @param[in] x XML moint-point node + * @param[out] yspec YANG stmt spec + * @retval 1 x is a mount-point: yspec may be set + * @retval 0 x is not a mount point + * @retval -1 Error + */ +int +xml_yang_mount_get(cxobj *x, + yang_stmt **yspec) +{ + int retval = -1; + cvec *cvv = NULL; + cg_var *cv; + yang_stmt *y; + yang_stmt *yu; + char *xpath = NULL; // XXX free it + int ret; + + if ((y = xml_spec(x)) == NULL) + goto fail; + if ((ret = yang_schema_mount_point(y)) < 0) + goto done; + if (ret == 0) + goto fail; + // XXX + if ((yu = yang_find(y, Y_UNKNOWN, "yangmnt:mount-point")) == NULL) + goto ok; + if (xml2xpath(x, NULL, &xpath) < 0) + goto done; + if ((cvv = yang_cvec_get(yu)) == NULL) + goto ok; + if ((cv = cvec_find(cvv, xpath)) == NULL) + goto ok; + if (yspec) + *yspec = cv_void_get(cv); + ok: + retval = 1; + done: + if (xpath) + free(xpath); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Set yangspec mount-point + * + * Stored in a separate structure (not in XML config tree) + * @param[in] x XML moint-point node + * @param[in] yspec Yangspec for this mount-point (consumed) + * @retval 0 OK + * @retval -1 Error + */ +int +xml_yang_mount_set(cxobj *x, + yang_stmt *yspec) +{ + cg_var *cv; + yang_stmt *y; + yang_stmt *yu; + yang_stmt *yspec0; + char *xpath = NULL; + cvec *cvv; + + if ((y = xml_spec(x)) == NULL || + (yu = yang_find(y, Y_UNKNOWN, "yangmnt:mount-point")) == NULL){ + goto done; + } + if (xml2xpath(x, NULL, &xpath) < 0) + goto done; + if ((cvv = yang_cvec_get(yu)) != NULL && + (cv = cvec_find(cvv, xpath)) != NULL && + (yspec0 = cv_void_get(cv)) != NULL){ + assert(0); + ys_free(yspec0); + cv_void_set(cv, NULL); + } + else if ((cv = yang_cvec_add(yu, CGV_VOID, xpath)) == NULL) + return -1; + cv_void_set(cv, yspec); + done: + if (xpath) + free(xpath); + return 0; +} + +/*! Free all yspec yang-mounts + * @aparm[in] cvv Cligen-variable vector containing xpath -> yspec mapping + */ +int +xml_yang_mount_freeall(cvec *cvv) +{ + cg_var *cv = NULL; + yang_stmt *ys; + + cv = NULL; + while ((cv = cvec_each(cvv, cv)) != NULL){ + if ((ys = cv_void_get(cv)) != NULL) + ys_free(ys); + } + return 0; +} + + /*! Find schema mounts - callback function for xml_apply * * @param[in] x XML node @@ -142,7 +278,6 @@ find_schema_mounts(cxobj *x, * Brute force: traverse whole XML, match all x that have ymount as yspec * Add yang-library state for all x * @param[in] h Clicon handle - * @param[in] yspec Yang spec * @param[in] xpath XML Xpath * @param[in] nsc XML Namespace context for xpath * @param[in,out] xret Existing XML tree, merge x into this @@ -159,20 +294,20 @@ find_schema_mounts(cxobj *x, * Alt: see snmp_yang2xml to get instances instead of brute force traverse of whole tree */ static int -schema_mounts_yang_library(clicon_handle h, - yang_stmt *yspec, - char *xpath, - cvec *nsc, - cxobj **xret, - cxobj **xerr) +yang_schema_mount_statedata_yanglib(clicon_handle h, + char *xpath, + cvec *nsc, + cxobj **xret, + cxobj **xerr) { - int retval = -1; - cvec *cvv = NULL; - cg_var *cv; - cxobj *xmp; /* xml mount-point */ - cxobj *xylib = NULL; /* xml yang-lib */ - cbuf *cb = NULL; - char *msid = "mount-point"; /* modules-set-id dummy */ + int retval = -1; + cvec *cvv = NULL; + cg_var *cv; + cxobj *xmp; /* xml mount-point */ + cxobj *yanglib = NULL; /* xml yang-lib */ + cbuf *cb = NULL; + yang_stmt *yspec; + int ret; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "clicon buffer"); @@ -187,25 +322,21 @@ schema_mounts_yang_library(clicon_handle h, cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) { xmp = cv_void_get(cv); - /* addsub here */ - cbuf_reset(cb); - // XXX change yspec to mount-point - /* Build a cb string: ... */ - if (yang_modules_state_build(h, yspec, msid, 0, cb) < 0) + yanglib = NULL; + /* User callback */ + if (clixon_plugin_yang_mount_all(h, xmp, &yanglib) < 0) goto done; - /* Parse cb, x is on the form: ... - * Note, list is not sorted since it is state (should not be) - */ - if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &xylib, NULL) < 0){ - if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) - goto done; + if (yanglib == NULL) + continue; + yspec = clicon_dbspec_yang(h); + // if ((ret = xml_bind_yang(h, yanglib, YB_NONE, yspec, &xerr)) < 0) + if ((ret = xml_bind_yang0(h, yanglib, YB_MODULE, yspec, xerr)) < 0) + goto done; + if (ret == 0) goto fail; - } - if (xml_rootchild(xylib, 0, &xylib) < 0) + if (xml_addsub(xmp, yanglib) < 0) goto done; - if (xml_addsub(xmp, xylib) < 0) - goto done; - xylib = NULL; + yanglib = NULL; } retval = 1; done: @@ -219,7 +350,7 @@ schema_mounts_yang_library(clicon_handle h, goto done; } -/*! Get modules state according to RFC 8528 +/*! Get schema mount-point state according to RFC 8528 * * @param[in] h Clicon handle * @param[in] yspec Yang spec @@ -233,12 +364,12 @@ schema_mounts_yang_library(clicon_handle h, * @note Only "inline" specification of mounted schema supported, not "shared schema" */ int -schema_mounts_state_get(clicon_handle h, - yang_stmt *yspec, - char *xpath, - cvec *nsc, - cxobj **xret, - cxobj **xerr) +yang_schema_mount_statedata(clicon_handle h, + yang_stmt *yspec, + char *xpath, + cvec *nsc, + cxobj **xret, + cxobj **xerr) { int retval = -1; cbuf *cb = NULL; @@ -291,7 +422,7 @@ schema_mounts_state_get(clicon_handle h, goto fail; } /* Find mount-points and return yang-library state */ - if (0 && schema_mounts_yang_library(h, yspec, xpath, nsc, xret, xerr) < 0) + if (yang_schema_mount_statedata_yanglib(h, xpath, nsc, xret, xerr) < 0) goto done; ok: retval = 1; @@ -306,41 +437,85 @@ schema_mounts_state_get(clicon_handle h, goto done; } -/*! Callback for yang schema mount-point extension - * - * @param[in] h Clixon handle - * @param[in] yext Yang node of extension - * @param[in] ys Yang node of (unknown) statement belonging to extension - * @retval 0 OK - * @retval -1 Error - // XXX his may not even be necessary +/*! Get yanglib, parse it and mount it */ int -yang_schema_unknown(clicon_handle h, - yang_stmt *yext, - yang_stmt *ys) +yang_schema_yanglib_parse_mount(clicon_handle h, + cxobj *xt) +{ + int retval = -1; + cxobj *yanglib = NULL; + yang_stmt *yspec = NULL; + int ret; + + if (clixon_plugin_yang_mount_all(h, xt, &yanglib) < 0) + goto done; + if (yanglib == NULL) + goto anydata; + /* Parse it and set mount-point */ + if ((yspec = yspec_new()) == NULL) + goto done; + if ((ret = yang_lib2yspec(h, yanglib, yspec)) < 0) + goto done; + if (ret == 0) + goto anydata; +#ifdef YANG_SCHEMA_MOUNT_YANG_LIB_FORCE + /* XXX: Ensure yang-lib is always there otherwise get state dont work for mountpoint */ + if (yang_spec_parse_module(h, "ietf-yang-library", "2019-01-04", yspec) < 0) + goto done; +#endif + if (xml_yang_mount_set(xt, yspec) < 0) + goto done; + retval = 1; + done: + if (yspec) + ys_free(yspec); + if (yanglib) + xml_free(yanglib); + return retval; + anydata: // Treat as anydata + retval = 0; + goto done; +} + +/*! Check if XML nod is mount-point and return matching YANG child + * @param[in] h Clicon handle + * @param[in] x1 XML node + * @param[in] x1c A child of x1 + * @param[out] yc YANG child + * @retval 1 OK, yc contains child + * @retval 0 No such child + * @retval -1 Error + * XXX maybe not needed + */ +int +yang_schema_get_child(clicon_handle h, + cxobj *x1, + cxobj *x1c, + yang_stmt **yc) { int retval = -1; - char *extname; - char *modname; - yang_stmt *ymod; - cg_var *cv; - char *label; + yang_stmt *yspec1; + yang_stmt *ymod1 = NULL; + char *x1cname; + int ret; - ymod = ys_module(yext); - modname = yang_argument_get(ymod); - extname = yang_argument_get(yext); - if (strcmp(modname, "ietf-yang-schema-mount") != 0 || strcmp(extname, "mount-point") != 0) - goto ok; - if ((cv = yang_cv_get(ys)) == NULL){ - clicon_err(OE_YANG, 0, "mount-point extension must have label"); + x1cname = xml_name(x1c); + if ((ret = xml_yang_mount_get(x1, &yspec1)) < 0) goto done; + if (ret == 1 && yspec1 != NULL){ + if (ys_module_by_xml(yspec1, x1c, &ymod1) <0) + goto done; + if (ymod1 != NULL) + *yc = yang_find_datanode(ymod1, x1cname); + else{ /* It is in fact a mountpoint, there is a yang mount, but it is not found */ + goto fail; + } } - label = cv_string_get(cv); - clicon_debug(1, "%s Enabled extension:%s:%s label:%s", __FUNCTION__, modname, extname, label); - // XXX his may not even be necessary - ok: - retval = 0; + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } diff --git a/test/test_yang_schema_mount.sh b/test/test_yang_schema_mount.sh index 913102d8..d22a894b 100755 --- a/test/test_yang_schema_mount.sh +++ b/test/test_yang_schema_mount.sh @@ -11,7 +11,6 @@ if true; then # enable YANG_SCHEMA_MOUNT if [ -z "${CLIXON_YANG_PATCH}" -a "$s" = $0 ]; then exit 0; else return 0; fi fi - APPNAME=example cfg=$dir/conf_mount.xml @@ -33,6 +32,7 @@ cat < $cfg $dir true true + true EOF @@ -45,13 +45,16 @@ module clixon-example{ prefix yangmnt; } container top{ - list root{ + list mylist{ key name; leaf name{ type string; } - yangmnt:mount-point "myroot"{ - description "Root for other yang models"; + container root{ + presence "Otherwise root is not visible"; + yangmnt:mount-point "myroot"{ + description "Root for other yang models"; + } } } } @@ -73,19 +76,19 @@ fi new "wait backend" wait_backend -new "Add two mountpoints: x and y" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "xy" "" "" +new "Add two mountpoints: x and y" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "xy" "" "" new "netconf commit" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" new "Retrieve schema-mounts with Operation" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "clixon-exampletrue" -if true; then new "get yang-lib at mountpoint" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" ">" "xy" -fi +# XXX maybe too many yangs here, difficult to maintain +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" ">" "x +defaultclixon-autocli2022-02-11http://clicon.org/autocliclixon-example2022-11-01urn:example:clixonietf-datastores2018-02-14urn:ietf:params:xml:ns:yang:ietf-datastoresietf-inet-types2021-02-22urn:ietf:params:xml:ns:yang:ietf-inet-typesietf-yang-library2019-01-04urn:ietf:params:xml:ns:yang:ietf-yang-libraryietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesmount-pointydefaultclixon-autocli2022-02-11http://clicon.org/autocliclixon-example2022-11-01urn:example:clixonietf-datastores2018-02-14urn:ietf:params:xml:ns:yang:ietf-datastoresietf-inet-types2021-02-22urn:ietf:params:xml:ns:yang:ietf-inet-typesietf-yang-library2019-01-04urn:ietf:params:xml:ns:yang:ietf-yang-libraryietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesmount-point" if [ $BE -ne 0 ]; then new "Kill backend" @@ -98,7 +101,6 @@ if [ $BE -ne 0 ]; then stop_backend -f $cfg fi - rm -rf $dir new "endtest"