From 2fc37d247097ddfe4e0359d8d61d702a2c60ddd7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 3 May 2019 14:03:10 +0200 Subject: [PATCH] Yang "unique" feature supported according to RFC 7950 7.8.3 --- CHANGELOG.md | 2 + README.md | 1 - lib/clixon/clixon_netconf_lib.h | 1 + lib/src/clixon_netconf_lib.c | 43 ++++++ lib/src/clixon_xml_map.c | 165 +++++++++++++++++++++++ lib/src/clixon_yang.c | 20 ++- test/test_unique.sh | 226 ++++++++++++++++++++++++++++++++ 7 files changed, 455 insertions(+), 3 deletions(-) create mode 100755 test/test_unique.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7f8219..7f9e91c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## 3.10.0/4.0.0 (Upcoming) ### Major New features +* Yang "unique" feature supported + * See RFC 7950 7.8.3 * Persistent CLI history: [Preserve CLI command history across sessions. The up/down arrows](https://github.com/clicon/clixon/issues/79) * The design is similar to bash history: * The CLI loads/saves its complete history to a file on entry and exit, respectively diff --git a/README.md b/README.md index 5d195282..e01bafcd 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,6 @@ Clixon follows: However, the following YANG syntax modules are not implemented: - deviation - min/max-elements -- unique - action - refine - Yang extended Xpath functions: re-match, deref, derived-from, derived-from-or-self, enum-value, bit-is-set diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 01a7ee2f..9a43bbb2 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -66,6 +66,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message_xml(cxobj **xret, char *message); +int netconf_data_not_unique(cbuf *cb, cxobj *x, cvec *cvk); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_module_load(clicon_handle h); char *netconf_db_find(cxobj *xn, char *name); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 45956aa9..2dbb4d21 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -966,6 +966,48 @@ netconf_malformed_message_xml(cxobj **xret, return retval; } +/*! Create Netconf data-not-unique error message according to RFC 7950 15.1 + * + * A NETCONF operation would result in configuration data where a + * "unique" constraint is invalidated. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] x List element containing duplicate + * @param[in] cvk List of comonents in x that are non-unique + * @see RFC7950 Sec 15.1 + */ +int +netconf_data_not_unique(cbuf *cb, + cxobj *x, + cvec *cvk) +{ + int retval = -1; + cg_var *cvi = NULL; + cxobj *xi; + + if (cprintf(cb, "" + "protocol" + "operation-failed" + "data-not-unique" + "error" + "") < 0) + goto err; + while ((cvi = cvec_each(cvk, cvi)) != NULL){ + if ((xi = xml_find(x, cv_string_get(cvi))) == NULL) + continue; /* ignore, shouldnt happen */ + cprintf(cb, ""); + clicon_xml2cbuf(cb, xi, 0, 0); + cprintf(cb, ""); + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + /*! Help function: merge - check yang - if error make netconf errmsg * @param[in] x XML tree * @param[in] yspec Yang spec @@ -1006,6 +1048,7 @@ netconf_trymerge(cxobj *x, return retval; } + /*! Load ietf netconf yang module and set enabled features * The features added are (in order): * candidate (8.3) diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 59519da0..1504f203 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -633,6 +633,167 @@ check_mandatory(cxobj *xt, goto done; } +/*! New elemnt last in list, check if already exists if sp return -1 + * @param[in] vec Vector of existing entries (new is last) + * @param[in] i1 The new entry is placed at vec[i1] + * @param[in] vlen Lenght of entry + * @retval 0 OK, entry is unique + * @retval -1 Duplicate detected + * @note This is currently linear complexity. It could be improved by inserting new element sorted and binary search. + */ +static int +check_insert_duplicate(char **vec, + int i1, + int vlen) +{ + int i; + int v; + char *b; + + for (i=0; iy-unique - "a" + * xt->x -> ab + * x -> bc + * x -> ab + */ +static int +check_unique_parent(cxobj *xt, + cbuf *cbret) +{ + int retval = -1; + cxobj *x = NULL; + yang_stmt *y; + yang_stmt *yp = NULL; /* previous in list */ + yang_stmt *yu; + int ret; + + /* */ + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) == NULL) + continue; + if (y == yp) /* If same yang as previous x, then skip (eg same list) */ + continue; + yp = y; + if (yang_keyword_get(y) != Y_LIST) + continue; + yu = NULL; + while ((yu = yn_each(y, yu)) != NULL) { + if (yang_keyword_get(yu) != Y_UNIQUE) + continue; + /* Here is a list w unique constraints identified by: + * its first element x, its yang spec y, its parent xt, and + * a unique yang spec yu, + */ + if ((ret = check_unique_list(x, xt, y, yu, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + } + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + + /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. @@ -831,6 +992,10 @@ xml_yang_validate_all(cxobj *xt, default: break; } + if ((ret = check_unique_parent(xt, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; /* must sub-node RFC 7950 Sec 7.5.3. Can be several. * XXX. use yang path instead? */ yc = NULL; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 525fc51b..f95db268 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1213,7 +1213,6 @@ yang_print_cbuf(cbuf *cb, return 0; } - /*! Populate yang leafs after parsing. Create cv and fill it in. * * Populate leaf in 2nd round of yang parsing, now that context is complete: @@ -1308,7 +1307,8 @@ ys_populate_list(yang_stmt *ys, if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL) return 0; - cvec_free(ys->ys_cvec); + if (ys->ys_cvec) + cvec_free(ys->ys_cvec); if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL) return -1; return 0; @@ -1681,6 +1681,18 @@ ys_populate_feature(clicon_handle h, return retval; } +/*! Populate the unique statement with a cvec + */ +static int +ys_populate_unique(yang_stmt *ys) +{ + if (ys->ys_cvec) + cvec_free(ys->ys_cvec); + if ((ys->ys_cvec = yang_arg2cvec(ys, " ")) == NULL) + return -1; + return 0; +} + /*! Populate unknown node with extension */ static int @@ -1770,6 +1782,10 @@ ys_populate(yang_stmt *ys, if (ys_populate_identity(ys, NULL) < 0) goto done; break; + case Y_UNIQUE: + if (ys_populate_unique(ys) < 0) + goto done; + break; case Y_UNKNOWN: if (ys_populate_unknown(ys) < 0) goto done; diff --git a/test/test_unique.sh b/test/test_unique.sh new file mode 100755 index 00000000..799a51bb --- /dev/null +++ b/test/test_unique.sh @@ -0,0 +1,226 @@ +#!/bin/bash +# Yang list unique tests +# Use example in RFC7890 7.8.3.1, modify fields and also test a variant with +# a single unique identifier (rfc example has two) +# The test adds the rfc conf that fails, then one that passes, then makes add +# to fail it and then del to pass it. +# Then makes a fail / pass test on the single field case +# Last, a complex unsorted list with several sub-elements. + +# 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/unique.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + +EOF + +# Example (the list server part) from RFC7950 Sec 7.8.3.1 w changed types +cat < $fyang +module unique{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix un; + container c{ + leaf a{ + type string; + } + list server { + description "RFC7950 7.8.3.1"; + key "name"; + unique "ip port"; + leaf name { + type string; + } + leaf ip { + type string; + } + leaf port { + type uint16; + } + } + list other { + description "random inserted data"; + key a; + leaf a{ + type string; + } + } + list single { + description "similar with just a single unique field"; + key "name"; + unique "ip"; + leaf name { + type string; + } + leaf ip { + type string; + } + } + leaf b{ + type string; + } + } +} +EOF + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + # start new backend + start_backend -s init -f $cfg + + new "waiting" + sleep $RCWAIT +fi + +# RFC test two-field caes +new "Add not valid example" +expecteof "$clixon_netconf -qf $cfg" 0 'replace + smtp + 192.0.2.1 + 25 + + + http + 192.0.2.1 + 25 + +]]>]]>' "^]]>]]>$" + +new "netconf validate (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$' + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "Add valid example" +expecteof "$clixon_netconf -qf $cfg" 0 'replace + smtp + 192.0.2.1 + 25 + + + http + 192.0.2.1 + + + ftp + 192.0.2.1 + +]]>]]>' "^]]>]]>$" + +new "netconf validate ok" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "make it invalid by adding port to ftp entry" +expecteof "$clixon_netconf -qf $cfg" 0 'noneftp25 +]]>]]>' "^]]>]]>$" + +new "netconf validate (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$' + +new "make it valid by deleting port from smtp entry" +expecteof "$clixon_netconf -qf $cfg" 0 'nonesmtp25 +]]>]]>' '^]]>]]>$' + +new "netconf validate ok" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# Then test single-field case +new "Add not valid example" +expecteof "$clixon_netconf -qf $cfg" 0 'replace + smtp + 192.0.2.1 + + + http + 192.0.2.1 + +]]>]]>' "^]]>]]>$" + +new "netconf validate (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^protocoloperation-faileddata-not-uniqueerror192.0.2.1]]>]]>$' + +new "make valid by replacing IP of http entry" +expecteof "$clixon_netconf -qf $cfg" 0 'nonehttp178.23.34.1 +]]>]]>' "^]]>]]>$" + +new "netconf validate ok" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# Then test composite case (detect duplicates among other elements) +# and also unordered + +new "Add not valid example" +expecteof "$clixon_netconf -qf $cfg" 0 'replace + other + + smtp + 192.0.2.1 + + other + + smtp + 192.0.2.1 + 25 + + + http + 192.0.2.1 + + xx + + http + 192.0.2.1 + 25 + +]]>]]>' "^]]>]]>$" + +new "netconf validate (should fail)" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^protocoloperation-faileddata-not-uniqueerror192.0.2.125]]>]]>$' + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +if [ $BE -eq 0 ]; then + exit # BE +fi + +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 + +rm -rf $dir