From 2e6d9167f2beecef20ac014f0bd35fb9b433be08 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 15 Nov 2023 12:12:42 +0100 Subject: [PATCH] Mount-point support for restconf --- apps/cli/cli_common.c | 2 +- example/main/README.md | 25 +++++++++ example/main/example_cli.c | 4 +- example/main/example_restconf.c | 81 +++++++++++++++++++++++++++++- lib/src/clixon_path.c | 24 ++++----- lib/src/clixon_xpath.c | 2 +- lib/src/clixon_yang_schema_mount.c | 6 ++- test/test_xpath_canonical.sh | 2 +- test/test_yang_schema_mount.sh | 40 ++++++++++++++- 9 files changed, 164 insertions(+), 22 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index a1b78a29..fd822a04 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -275,7 +275,7 @@ identityref_add_ns(cxobj *x, /*! Given a top-level yspec and mountpoint xpath compute a set of * - * Manipulate top-level and a mointpoint: + * Manipulate top-level and a mountpoint: * YSPEC: yspec0 yspec1 * XML: top0-->bot0-->top1-->bot1 * API-PATH: api-path0 api-path1 diff --git a/example/main/README.md b/example/main/README.md index 2b8d820b..c57727c1 100644 --- a/example/main/README.md +++ b/example/main/README.md @@ -13,6 +13,7 @@ * [Systemd](#systemd) * [Docker](#docker) * [Plugins](#plugins) + * [Mount-points](#mount-points) ## Background @@ -446,3 +447,27 @@ static clixon_plugin_api api = { .ca_interrupt=NULL, /* cligen_interrupt_cb_t */ }; ``` + +## Mount-points + +You can set-up the example for a simple RFC 8528 Yang schema mount. A single top-level yang can be defined to be mounted.: + +1. Enable CLICON_YANG_SCHEMA_MOUNT +2. Define the mount-point using the ietf-yang-schema-mount mount-point extension +3. Start the backend, cli and restconf with `-- -m -M `, where `name` and `urn` is the name and namespace of the mounted YANG, respectively. + +A simple example on how to define a mount-point +``` + import ietf-yang-schema-mount { + prefix yangmnt; + } + container root{ + presence "Otherwise root is not visible"; + yangmnt:mount-point "mylabel"{ + description "Root for other yang models"; + } + } +``` + +CLI completion of the mounted part is not implemented in the example, see the +clixon-controller `controller_cligen_treeref_wrap()` for an example. \ No newline at end of file diff --git a/example/main/example_cli.c b/example/main/example_cli.c index ea083adc..ec33e707 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -58,7 +58,7 @@ /*! Yang schema mount * - * Start backend with -- -m -M + * Start cli with -- -m -M * Mount this yang on mountpoint */ static char *_mount_yang = NULL; @@ -313,6 +313,8 @@ clixon_plugin_init(clicon_handle h) clicon_err(OE_PLUGIN, EINVAL, "Both -m and -M must be given for mounts"); goto done; } + /* XXX Not implemented: CLI completion for mountpoints, see clixon-controller + */ return &api; done: return NULL; diff --git a/example/main/example_restconf.c b/example/main/example_restconf.c index 264731dc..317d8b33 100644 --- a/example/main/example_restconf.c +++ b/example/main/example_restconf.c @@ -51,12 +51,20 @@ /* Command line options to be passed to getopt(3) */ -#define RESTCONF_EXAMPLE_OPTS "" +#define RESTCONF_EXAMPLE_OPTS "m:M:" static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char Pad64 = '='; +/*! Yang schema mount + * + * Start restconf with -- -m -M + * Mount this yang on mountpoint + */ +static char *_mount_yang = NULL; +static char *_mount_namespace = NULL; + /* skips all whitespace anywhere. converts characters, four at a time, starting at (or after) src from base - 64 numbers into three 8 bit bytes in the target area. @@ -350,6 +358,62 @@ example_restconf_start(clicon_handle h) return 0; } +/*! 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] config If '0' all data nodes in the mounted schema are read-only + * @param[out] validate Do or dont do full RFC 7950 validation + * @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 +restconf_yang_mount(clicon_handle h, + cxobj *xt, + int *config, + validate_level *vl, + cxobj **yanglib) +{ + int retval = -1; + cbuf *cb = NULL; + + if (config) + *config = 1; + if (vl) + *vl = VL_FULL; + if (yanglib && _mount_yang){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, ""); + cprintf(cb, ""); + cprintf(cb, "mylabel"); // XXX label in test_yang_schema_mount + cprintf(cb, ""); + /* In yang name+namespace is mandatory, but not revision */ + cprintf(cb, "%s", _mount_yang); // mandatory + cprintf(cb, "%s", _mount_namespace); // mandatory + // cprintf(cb, "2022-11-01"); + 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; +} + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { @@ -357,7 +421,8 @@ static clixon_plugin_api api = { clixon_plugin_init, /* init */ example_restconf_start,/* start */ NULL, /* exit */ - .ca_auth=example_restconf_credentials /* auth */ + .ca_auth=example_restconf_credentials, /* auth */ + .ca_yang_mount=restconf_yang_mount, /* RFC 8528 schema mount */ }; /*! Restconf plugin initialization @@ -382,11 +447,23 @@ clixon_plugin_init(clicon_handle h) optind = 1; while ((c = getopt(argc, argv, RESTCONF_EXAMPLE_OPTS)) != -1) switch (c) { + case 'm': + _mount_yang = optarg; + break; + case 'M': + _mount_namespace = optarg; + break; default: break; } + if ((_mount_yang && !_mount_namespace) || (!_mount_yang && _mount_namespace)){ + clicon_err(OE_PLUGIN, EINVAL, "Both -m and -M must be given for mounts"); + goto done; + } /* Register local netconf rpc client (note not backend rpc client) */ if (rpc_callback_register(h, restconf_client_rpc, NULL, "urn:example:clixon", "client-rpc") < 0) return NULL; return &api; + done: + return NULL; } diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index d1156032..8c6bcd2b 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -808,21 +808,21 @@ api_path2xpath_cvv(cvec *api_path, cprintf(xpath, "%s", name); } - /* If x/y is mountpoint, pass moint yspec to children */ + /* If x/y is mountpoint, pass mount yspec to children */ if ((ret = yang_schema_mount_point(y)) < 0) goto done; if (ret == 1){ yang_stmt *y1 = NULL; if (xml_nsctx_yangspec(yspec, &nsc) < 0) goto done; + /* cf xml_bind_yang0_opt/xml_yang_mount_get */ if (yang_mount_get(y, cbuf_get(xpath), &y1) < 0) goto done; - if (y1 == NULL || yang_keyword_get(y1) != Y_SPEC){ - clicon_err(OE_YANG, 0, "No such mountpoint %s", cbuf_get(xpath)); - goto done; + if (y1 != NULL){ + y = y1; + yspec = y1; + root = 1; } - yspec = y1; - root = 1; } if (prefix){ free(prefix); @@ -1151,7 +1151,7 @@ api_path2xml_vec(char **vec, if (xmlns_set(x, NULL, namespace) < 0) goto done; } - /* If x/y is mountpoint, pass moint yspec to children */ + /* If x/y is mountpoint, pass mount yspec to children */ if ((ret = yang_schema_mount_point(y)) < 0) goto done; if (ret == 1){ @@ -1164,13 +1164,11 @@ api_path2xml_vec(char **vec, clicon_err(OE_YANG, 0, "No xpath from xml"); goto done; } - if (yang_mount_get(y, xpath, &y1) < 0) + /* cf xml_bind_yang0_opt/xml_yang_mount_get */ + if (yang_mount_get(y, xpath, &y1) < 0) goto done; - if (y1 == NULL){ - clicon_err(OE_YANG, 0, "No such mountpoint %s", xpath); - goto done; - } - y = y1; + if (y1 != NULL) + y = y1; } if ((retval = api_path2xml_vec(vec+1, nvec-1, x, y, diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index e94efb60..782482ac 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -1105,7 +1105,7 @@ xpath_traverse_canonical(xpath_tree *xs, * if (reason) cbuf_free(reason); * @endcode * @note Unsolvable issue of mountpoints, eg an xpath of //x:foo where foo is under one or several - * mointpoints: a well-defined namespace cannot be determined. Therefore just allow + * mountpoints: a well-defined namespace cannot be determined. Therefore just allow * inconsistencies and hope that it will be covered by other code * @see xpath2xml */ diff --git a/lib/src/clixon_yang_schema_mount.c b/lib/src/clixon_yang_schema_mount.c index a59bacaf..7e8d7a68 100644 --- a/lib/src/clixon_yang_schema_mount.c +++ b/lib/src/clixon_yang_schema_mount.c @@ -153,6 +153,7 @@ yang_mount_get(yang_stmt *y, cvec *cvv = NULL; cg_var *cv; + clicon_debug(CLIXON_DBG_DEFAULT, "%s %s %p", __FUNCTION__, xpath, y); /* Special value in yang unknown node for mount-points: mapping from xpath->mounted yspec */ if ((cvv = yang_cvec_get(y)) != NULL && (cv = cvec_find(cvv, xpath)) != NULL && @@ -181,6 +182,7 @@ yang_mount_set(yang_stmt *y, cg_var *cv; cg_var *cv2; + clicon_debug(CLIXON_DBG_DEFAULT, "%s %s %p", __FUNCTION__, xpath, y); if ((cvv = yang_cvec_get(y)) != NULL && (cv = cvec_find(cvv, xpath)) != NULL && (yspec0 = cv_void_get(cv)) != NULL){ @@ -210,7 +212,7 @@ yang_mount_set(yang_stmt *y, /*! Get yangspec mount-point * * @param[in] h Clixon handle - * @param[in] x XML moint-point node + * @param[in] x XML mount-point node * @param[out] vallevel Do or dont do full RFC 7950 validation if given * @param[out] yspec YANG stmt spec * @retval 1 x is a mount-point: yspec may be set @@ -274,7 +276,7 @@ xml_yang_mount_get(clicon_handle h, /*! Set yangspec mount-point via XML mount-point node * * Stored in a separate structure (not in XML config tree) - * @param[in] x XML moint-point node + * @param[in] x XML mount-point node * @param[in] yspec Yangspec for this mount-point (consumed) * @retval 0 OK * @retval -1 Error diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index f800a534..3e7854d6 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -60,7 +60,7 @@ new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2>&1)" 0 "/x/j:y: No namespace found for prefix" if false; then -# No, with mointpoints I cant fail unknown prefix, see comment in xpath2canonical +# No, with mountpoints I cant fail unknown prefix, see comment in xpath2canonical new "xpath canonical form (wrong namespace should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>&1)" 0 "/i:x/j:y: No yang found for namespace" fi diff --git a/test/test_yang_schema_mount.sh b/test/test_yang_schema_mount.sh index baeddabe..7bfd30c5 100755 --- a/test/test_yang_schema_mount.sh +++ b/test/test_yang_schema_mount.sh @@ -16,16 +16,23 @@ fyang0=$dir/clixon-mount0.yang fyang1=$dir/clixon-mount1.yang fyang2=$dir/clixon-mount2.yang +CFD=$dir/conf.d +test -d $CFD || mkdir -p $CFD + AUTOCLI=$(autocli_config clixon-\* kw-nokey false) +RESTCONFIG=$(restconf_config none false) cat < $cfg $cfg + $CFD + clixon-restconf:allow-auth-none ${YANG_INSTALLDIR} ${dir} $fyang true $dir + /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/run/$APPNAME.sock @@ -36,6 +43,11 @@ cat < $cfg true true true + +EOF + +cat < $CFD/autocli.xml + false kw-nokey @@ -48,7 +60,14 @@ cat < $cfg EOF -# ${AUTOCLI} + +# Define default restconfig config: RESTCONFIG + +cat < $CFD/restconf.xml + + $RESTCONFIG + +EOF cat < $fyang module clixon-example{ @@ -172,6 +191,17 @@ fi new "wait backend" wait_backend +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + + new "start restconf daemon" + start_restconf -f $cfg -- -m clixon-mount0 -M urn:example:mount0 +fi + +new "wait restconf" +wait_restconf + new "Add two mountpoints: x and y" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "xy" "" "" @@ -205,6 +235,14 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "cli show config" expectpart "$($clixon_cli -1 -f $cfg show config xml -- -m clixon-mount0 -M urn:example:mount0)" 0 "xx1bary" +new "restconf get config mntpoint" +expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data/clixon-example:top/mylist=x/root)" 0 "HTTP/$HVER 200" 'x1barmylabelclixon-mount0urn:example:mount0' + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill