diff --git a/example/main/example_backend.c b/example/main/example_backend.c index 0340189f..9253dea8 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -36,6 +36,8 @@ * 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 + * -m Mount this yang on mountpoint + * -M Namespace of mountpoint, note both -m and -M must exist * -n Notification streams example * -r enable the reset function * -s enable the state function @@ -68,7 +70,7 @@ #include /* Command line options to be passed to getopt(3) */ -#define BACKEND_EXAMPLE_OPTS "a:nrsS:x:iuUtV:" +#define BACKEND_EXAMPLE_OPTS "a:m:M:nrsS:x:iuUtV:" /* Enabling this improves performance in tests, but there may trigger the "double XPath" * problem. @@ -84,6 +86,14 @@ */ static char *_action_instanceid = NULL; +/*! Yang schema mount + * + * Start backend with -- -m -M + * Mount this yang on mountpoint + */ +static char *_mount_yang = NULL; +static char *_mount_namespace = NULL; + /*! Notification stream * * Enable notification streams for netconf/restconf @@ -936,18 +946,19 @@ main_yang_mount(clicon_handle h, *config = 1; if (vl) *vl = VL_FULL; - if (yanglib){ + if (yanglib && _mount_yang){ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } cprintf(cb, ""); cprintf(cb, ""); - cprintf(cb, "mount"); + cprintf(cb, "mylabel"); // XXX label in test_yang_schema_mount cprintf(cb, ""); - cprintf(cb, "clixon-example"); - cprintf(cb, "2022-11-01"); - cprintf(cb, "urn:example:urn"); + /* 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, ""); @@ -1426,6 +1437,12 @@ clixon_plugin_init(clicon_handle h) case 'a': _action_instanceid = optarg; break; + case 'm': + _mount_yang = optarg; + break; + case 'M': + _mount_namespace = optarg; + break; case 'n': _notification_stream = 1; break; @@ -1457,7 +1474,10 @@ clixon_plugin_init(clicon_handle h) _validate_fail_xpath = optarg; 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; + } if (_state_file){ api.ca_statedata = example_statefile; /* Switch state data callback */ if (_state_xpath){ diff --git a/example/main/example_cli.c b/example/main/example_cli.c index 1bfcd58c..9d6a78e8 100644 --- a/example/main/example_cli.c +++ b/example/main/example_cli.c @@ -33,6 +33,10 @@ ***** END LICENSE BLOCK ***** * + * The example have the following optional arguments that you can pass as + * argc/argv after -- in clixon_cli: + * -m Mount this yang on mountpoint + * -M Namespace of mountpoint, note both -m and -M must exist */ #include #include @@ -52,6 +56,14 @@ #include #include +/*! Yang schema mount + * + * Start backend with -- -m -M + * Mount this yang on mountpoint + */ +static char *_mount_yang = NULL; +static char *_mount_namespace = NULL; + /*! Example cli function */ int mycallback(clicon_handle h, cvec *cvv, cvec *argv) @@ -135,34 +147,6 @@ example_client_rpc(clicon_handle h, return retval; } -#ifndef CLIXON_STATIC_PLUGINS -static clixon_plugin_api api = { - "example", /* name */ - clixon_plugin_init, /* init */ - NULL, /* start */ - NULL, /* exit */ - .ca_prompt=NULL, /* cli_prompthook_t */ - .ca_suspend=NULL, /* cligen_susp_cb_t */ - .ca_interrupt=NULL, /* cligen_interrupt_cb_t */ -}; - -/*! CLI plugin initialization - * @param[in] h Clixon handle - * @retval NULL Error with clicon_err set - * @retval api Pointer to API struct - */ -clixon_plugin_api * -clixon_plugin_init(clicon_handle h) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - srandom(tv.tv_usec); - - return &api; -} -#endif /* CLIXON_STATIC_PLUGINS */ - /*! Translate function from an original value to a new. * In this case, assume string and increment characters, eg HAL->IBM */ @@ -185,3 +169,111 @@ cli_incstr(cligen_handle h, str[i]++; 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 +example_cli_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, "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; +} + +#ifndef CLIXON_STATIC_PLUGINS +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + .ca_prompt=NULL, /* cli_prompthook_t */ + .ca_suspend=NULL, /* cligen_susp_cb_t */ + .ca_interrupt=NULL, /* cligen_interrupt_cb_t */ + .ca_yang_mount=example_cli_yang_mount /* RFC 8528 schema mount */ +}; + +/*! CLI plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + struct timeval tv; + int c; + int argc; /* command-line options (after --) */ + char **argv; + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + /* Get user command-line options (after --) */ + if (clicon_argv_get(h, &argc, &argv) < 0) + goto done; + opterr = 0; + optind = 1; + while ((c = getopt(argc, argv, "m:M:")) != -1) + switch (c) { + case 'm': + _mount_yang = optarg; + break; + case 'M': + _mount_namespace = optarg; + 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; + } + return &api; + done: + return NULL; +} +#endif /* CLIXON_STATIC_PLUGINS */ + diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index ec838d41..acd3174b 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -290,6 +290,8 @@ typedef int (datastore_upgrade_t)(clicon_handle h, const char *db, cxobj *xt, mo * @param[out] yanglib XML yang-lib module-set tree. Freed by caller. * @retval 0 OK * @retval -1 Error + * @note For optional fields, such as revision, the callback may omit it, but will be resolved + * at proper parse-time, but this is not visible in the yanglib return * @see RFC 8528 (schema-mount) and RFC 8525 (yang-lib) */ typedef int (yang_mount_t)(clicon_handle h, cxobj *xt, int *config, diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 6165dfcb..a3aa5000 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -828,7 +828,7 @@ yang_metadata_init(clicon_handle h) * @param[in] yspec Will be populated with YANGs, is consumed * @retval 1 OK * @retval 0 Parse error - * @retval -1 Error + * @retval -1 Error * @see xml_schema_add_mount_points * XXX: Ensure yang-lib is always there otherwise get state dont work for mountpoint */ @@ -855,8 +855,7 @@ yang_lib2yspec(clicon_handle h, xi = vec[i]; if ((name = xml_find_body(xi, "name")) == NULL) continue; - if ((revision = xml_find_body(xi, "revision")) == NULL) - continue; + revision = xml_find_body(xi, "revision"); if ((ymod = yang_find(yspec, Y_MODULE, name)) != NULL || (ymod = yang_find(yspec, Y_SUBMODULE, name)) != NULL){ /* Skip if matching or no revision @@ -866,7 +865,7 @@ yang_lib2yspec(clicon_handle h, modmin++; continue; } - if (strcmp(yang_argument_get(yrev), revision) == 0){ + if (revision && strcmp(yang_argument_get(yrev), revision) == 0){ modmin++; continue; } @@ -875,7 +874,8 @@ yang_lib2yspec(clicon_handle h, goto fail; } #ifdef YANG_SCHEMA_MOUNT_YANG_LIB_FORCE - /* XXX: Ensure yang-lib is always there otherwise get state dont work for mountpoint */ + /* Force add ietf-yang-library@2019-01-04 on all mount-points + otherwise get state dont work for mountpoint */ if ((ymod = yang_find(yspec, Y_MODULE, "ietf-yang-library")) != NULL && (yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL && strcmp(yang_argument_get(yrev), "2019-01-04") == 0){ diff --git a/lib/src/clixon_yang_schema_mount.c b/lib/src/clixon_yang_schema_mount.c index 374413d2..4359de65 100644 --- a/lib/src/clixon_yang_schema_mount.c +++ b/lib/src/clixon_yang_schema_mount.c @@ -255,7 +255,6 @@ xml_yang_mount_get(clicon_handle h, goto done; } - /*! Set yangspec mount-point via XML mount-point node * * Stored in a separate structure (not in XML config tree) @@ -389,6 +388,7 @@ yang_schema_mount_statedata_yanglib(clicon_handle h, } if (xml_apply(*xret, CX_ELMNT, find_schema_mounts, cvv) < 0) goto done; + yspec = clicon_dbspec_yang(h); cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) { xmp = cv_void_get(cv); @@ -398,7 +398,6 @@ yang_schema_mount_statedata_yanglib(clicon_handle h, goto done; if (yanglib == NULL) continue; - yspec = clicon_dbspec_yang(h); if ((ret = xml_bind_yang0(h, yanglib, YB_MODULE, yspec, xerr)) < 0) goto done; if (ret == 0) diff --git a/test/test_yang_schema_mount.sh b/test/test_yang_schema_mount.sh index a1a22ceb..774ce301 100755 --- a/test/test_yang_schema_mount.sh +++ b/test/test_yang_schema_mount.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # Test for RFC8528 YANG Schema Mount -# XXX No cli tests +# clixon-example is top-level, mounts clixon-mount1 +# The example extends the main example using -- -m -M for both backend and cli +# Extensive testing of mounted augment/uses: see fyang0 which includes fyang1+fyang2 # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -8,7 +10,13 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi APPNAME=example cfg=$dir/conf_mount.xml +clispec=$dir/automode.cli fyang=$dir/clixon-example.yang +fyang0=$dir/clixon-mount0.yang +fyang1=$dir/clixon-mount1.yang +fyang2=$dir/clixon-mount2.yang + +AUTOCLI=$(autocli_config clixon-\* kw-nokey false) cat < $cfg @@ -17,7 +25,7 @@ cat < $cfg ${dir} $fyang true - /usr/local/lib/$APPNAME/clispec + $dir /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock @@ -28,8 +36,19 @@ cat < $cfg true true true + + false + kw-nokey + true + + include clixon + enable + clixon-* + + EOF +# ${AUTOCLI} cat < $fyang module clixon-example{ @@ -47,7 +66,7 @@ module clixon-example{ } container root{ presence "Otherwise root is not visible"; - yangmnt:mount-point "myroot"{ + yangmnt:mount-point "mylabel"{ description "Root for other yang models"; } } @@ -56,6 +75,88 @@ module clixon-example{ } EOF +cat < $fyang0 +module clixon-mount0{ + yang-version 1.1; + namespace "urn:example:mount0"; + prefix m0; + import clixon-mount1 { + prefix m1; + } + import clixon-mount2 { + prefix m2; + } +} +EOF + +cat < $fyang1 +module clixon-mount1{ + yang-version 1.1; + namespace "urn:example:mount1"; + prefix m1; + container mount1{ + list mylist1{ + key name1; + leaf name1{ + type string; + } + } + } +} +EOF + +cat < $fyang2 +module clixon-mount2{ + yang-version 1.1; + namespace "urn:example:mount2"; + prefix m2; + import clixon-mount1 { + prefix m1; + } + grouping ag2 { + leaf option2{ + type string; + } + } + grouping ag1 { + container options { + leaf option1{ + type string; + } + uses ag2; + } + } + augment /m1:mount1/m1:mylist1 { + uses ag1; + } +} +EOF + +cat < $clispec +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; +CLICON_PLUGIN="example_cli"; + +# Autocli syntax tree operations +set @datamodel, cli_auto_set(); +merge @datamodel, cli_auto_merge(); +create @datamodel, cli_auto_create(); +delete("Delete a configuration item") @datamodel, cli_auto_del(); +validate("Validate changes"), cli_validate(); +commit("Commit the changes"), cli_commit(); +quit("Quit"), cli_quit(); +show("Show a particular state of the system"){ + configuration("Show configuration"), cli_show_auto_mode("candidate", "xml", true, false);{ + xml("Show configuration as XML"), cli_show_auto_mode("candidate", "xml", false, false); + cli("Show configuration as CLI commands"), cli_show_auto_mode("candidate", "cli", false, false, "report-all", "set "); + netconf("Show configuration as netconf edit-config operation"), cli_show_auto_mode("candidate", "netconf", false, false); + text("Show configuration as text"), cli_show_auto_mode("candidate", "text", false, false); + json("Show configuration as JSON"), cli_show_auto_mode("candidate", "json", false, false); + } + state("Show configuration and state"), cli_show_auto_mode("running", "xml", false, true); +} +EOF + new "test params: -f $cfg" if [ $BE -ne 0 ]; then @@ -64,8 +165,8 @@ if [ $BE -ne 0 ]; then if [ $? -ne 0 ]; then err fi - new "start backend -s init -f $cfg" - start_backend -s init -f $cfg + new "start backend -s init -f $cfg -- -m clixon-mount0 -M urn:example:mount0" + start_backend -s init -f $cfg -- -m clixon-mount0 -M urn:example:mount0 fi new "wait backend" @@ -78,14 +179,32 @@ 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" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "clixon-exampletrue" new "get yang-lib at mountpoint" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" ">" "xmountclixon-example2022-11-01urn:example:urnymountclixon-example2022-11-01urn:example:urn" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" ">" "xmylabelclixon-mount0urn:example:mount0ymylabelclixon-mount0urn:example:mount0" new "check there is statistics from mountpoint" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" 'mountpoint: /top/mylist\[name="x"\]/root' +new "Add data to mounts" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "xx1" "" "" + +new "netconf commit" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "Add mounted augment data 2" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "xx1bar" "" "" + +new "netconf commit" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "get mounted augment data" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "xx1foobary" + +new "cli show config" +expectpart "$($clixon_cli -1 -f $cfg show config xml -- -m clixon-mount0 -M urn:example:mount0)" 0 "xx1foobary" + if [ $BE -ne 0 ]; then new "Kill backend" # Check if premature kill diff --git a/yang/clixon/clixon-config@2023-05-01.yang b/yang/clixon/clixon-config@2023-05-01.yang index 20f1a91c..748293cb 100644 --- a/yang/clixon/clixon-config@2023-05-01.yang +++ b/yang/clixon/clixon-config@2023-05-01.yang @@ -519,7 +519,13 @@ module clixon-config { leaf CLICON_YANG_SCHEMA_MOUNT{ type boolean; description - "YANG schema mount, RFC 8528"; + "YANG schema mount, RFC 8528. + When enabled, mount-points as defined by the 'yangmnt:mount-point' extension can + be populated by other YANGs than the root. + This is controlled by the ca_yang_mount plugin callback by returning a assigning a + yanglib module-set section that corresponds to the mounted YANGs. + Also, schema mount statistics is added to state data + Further, autocli syntax is added by definining a tree resolve wrapper"; default false; } leaf CLICON_BACKEND_REGEXP {